    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