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