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