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