001 // Copyright 2011 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.tapestry5.corelib.components;
016
017 import org.apache.tapestry5.*;
018 import org.apache.tapestry5.annotations.Environmental;
019 import org.apache.tapestry5.annotations.Parameter;
020 import org.apache.tapestry5.annotations.Property;
021 import org.apache.tapestry5.corelib.base.AbstractField;
022 import org.apache.tapestry5.dom.Element;
023 import org.apache.tapestry5.ioc.annotations.Inject;
024 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
025 import org.apache.tapestry5.services.ComponentDefaultProvider;
026 import org.apache.tapestry5.services.Request;
027 import org.apache.tapestry5.services.javascript.JavaScriptSupport;
028
029 import java.util.Collections;
030 import java.util.List;
031 import java.util.Set;
032
033 /**
034 * A list of checkboxes, allowing selection of multiple items in a list.
035 * <p/>
036 * For an alternative component that can be used for similar purposes, see
037 * {@link Palette}.
038 *
039 * @tapestrydoc
040 * @see Form
041 * @see Palette
042 * @since 5.3
043 */
044 public class Checklist extends AbstractField
045 {
046
047 /**
048 * Model used to define the values and labels used when rendering the
049 * checklist.
050 */
051 @Parameter(required = true)
052 private SelectModel model;
053
054 /**
055 * The list of selected values from the
056 * {@link org.apache.tapestry5.SelectModel}. This will be updated when the
057 * form is submitted. If the value for the parameter is null, a new list
058 * will be created, otherwise the existing list will be cleared. If unbound,
059 * defaults to a property of the container matching this component's id.
060 */
061 @Parameter(required = true, autoconnect = true)
062 private List<Object> selected;
063
064 /**
065 * A ValueEncoder used to convert server-side objects (provided from the
066 * "source" parameter) into unique client-side strings (typically IDs) and
067 * back. Note: this component does NOT support ValueEncoders configured to
068 * be provided automatically by Tapestry.
069 */
070 @Parameter(required = true, allowNull = false)
071 private ValueEncoder<Object> encoder;
072
073 /**
074 * The object that will perform input validation. The validate binding prefix is generally used to provide
075 * this object in a declarative fashion.
076 */
077 @Parameter(defaultPrefix = BindingConstants.VALIDATE)
078 @SuppressWarnings("unchecked")
079 private FieldValidator<Object> validate;
080
081 @Inject
082 private Request request;
083
084 @Inject
085 private FieldValidationSupport fieldValidationSupport;
086
087 @Environmental
088 private ValidationTracker tracker;
089
090 @Inject
091 private ComponentResources componentResources;
092
093 @Inject
094 private ComponentDefaultProvider defaultProvider;
095
096 @Inject
097 private JavaScriptSupport javaScriptSupport;
098
099 @Property
100 private List<Renderable> availableOptions;
101
102 private MarkupWriter markupWriter;
103
104 private final class RenderRadio implements Renderable
105 {
106 private final OptionModel model;
107
108 private RenderRadio(final OptionModel model)
109 {
110 this.model = model;
111 }
112
113 public void render(MarkupWriter writer)
114 {
115 final String clientId = javaScriptSupport.allocateClientId(componentResources);
116
117 final String clientValue = encoder.toClient(model.getValue());
118
119 final Element checkbox = writer.element("input", "type", "checkbox", "id", clientId, "name", getControlName(), "value", clientValue);
120
121 if (getSelected().contains(model.getValue()))
122 {
123 checkbox.attribute("checked", "checked");
124 }
125 writer.end();
126
127 writer.element("label", "for", clientId);
128 writer.write(model.getLabel());
129 writer.end();
130 }
131 }
132
133 void setupRender(final MarkupWriter writer)
134 {
135 markupWriter = writer;
136
137 availableOptions = CollectionFactory.newList();
138
139 final SelectModelVisitor visitor = new SelectModelVisitor()
140 {
141 public void beginOptionGroup(final OptionGroupModel groupModel)
142 {
143 }
144
145 public void option(final OptionModel optionModel)
146 {
147 availableOptions.add(new RenderRadio(optionModel));
148 }
149
150 public void endOptionGroup(final OptionGroupModel groupModel)
151 {
152 }
153
154 };
155
156 model.visit(visitor);
157 }
158
159 @Override
160 protected void processSubmission(final String controlName)
161 {
162
163 final String[] parameters = request.getParameters(controlName);
164
165 List<Object> selected = this.selected;
166
167 if (selected == null)
168 {
169 selected = CollectionFactory.newList();
170 } else
171 {
172 selected.clear();
173 }
174
175 if (parameters != null)
176 {
177 for (final String value : parameters)
178 {
179 final Object objectValue = encoder.toValue(value);
180
181 selected.add(objectValue);
182 }
183
184 }
185
186 putPropertyNameIntoBeanValidationContext("selected");
187
188 try
189 {
190 this.fieldValidationSupport.validate(selected, this.componentResources, this.validate);
191
192 this.selected = selected;
193 } catch (final ValidationException e)
194 {
195 this.tracker.recordError(this, e.getMessage());
196 }
197
198 removePropertyNameFromBeanValidationContext();
199 }
200
201 Set<Object> getSelected()
202 {
203 if (selected == null)
204 {
205 return Collections.emptySet();
206 }
207
208 return CollectionFactory.newSet(selected);
209 }
210
211 /**
212 * Computes a default value for the "validate" parameter using
213 * {@link org.apache.tapestry5.services.FieldValidatorDefaultSource}.
214 */
215
216 Binding defaultValidate()
217 {
218 return this.defaultProvider.defaultValidatorBinding("selected", this.componentResources);
219 }
220
221 @Override
222 public boolean isRequired()
223 {
224 return validate.isRequired();
225 }
226 }
227