    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.commons.util.CollectionFactory;
    019import org.apache.tapestry5.corelib.base.AbstractField;
    020import org.apache.tapestry5.dom.Element;
    021import org.apache.tapestry5.http.services.Request;
    022import org.apache.tapestry5.ioc.annotations.Inject;
    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