    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