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