    001// Copyright 2007, 2008, 2009, 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
    015package org.apache.tapestry5.corelib.components;
    016
    017import org.apache.tapestry5.*;
    018import org.apache.tapestry5.annotations.Environmental;
    019import org.apache.tapestry5.annotations.Events;
    020import org.apache.tapestry5.annotations.Parameter;
    021import org.apache.tapestry5.internal.TapestryInternalUtils;
    022import org.apache.tapestry5.ioc.annotations.Inject;
    023import org.apache.tapestry5.services.ComponentDefaultProvider;
    024import org.apache.tapestry5.services.Environment;
    025import org.apache.tapestry5.services.FormSupport;
    026import org.apache.tapestry5.services.Request;
    027
    028/**
    029 * A wrapper component around some number of {@link Radio} components, used to organize the selection and define the
    030 * property to be edited. Examples of its use are in the {@link Radio} documentation.
    031 *
    032 * @tapestrydoc
    033 */
    034@Events(EventConstants.VALIDATE)
    035public class RadioGroup implements Field
    036{
    037    /**
    038     * The property read and updated by the group as a whole.
    039     */
    040    @Parameter(required = true, principal = true, autoconnect = true)
    041    private Object value;
    042
    043    /**
    044     * If true, then the field will render out with a disabled attribute (to turn off client-side behavior). Further, a
    045     * disabled field ignores any value in the request when the form is submitted.
    046     */
    047    @Parameter("false")
    048    private boolean disabled;
    049
    050    /**
    051     * The user presentable label for the field. If not provided, a reasonable label is generated from the component's
    052     * id, first by looking for a message key named "id-label" (substituting the component's actual id), then by
    053     * converting the actual id to a presentable string (for example, "userId" to "User Id").
    054     */
    055    @Parameter(defaultPrefix = BindingConstants.LITERAL)
    056    private String label;
    057
    058    /**
    059     * The id used to generate a page-unique client-side identifier for the component. If a component renders multiple
    060     * times, a suffix will be appended to the to id to ensure uniqueness. The uniqued value may be accessed via the
    061     * {@link #getClientId() clientId property}.
    062     */
    063    @Parameter(value = "prop:componentResources.id", defaultPrefix = BindingConstants.LITERAL)
    064    private String clientId;
    065
    066    /**
    067     * A ValueEncoder used to convert server-side objects (provided by the
    068     * selected Radio componnent's "value" parameter) into unique client-side
    069     * strings (typically IDs) and back. Note: this parameter may be OMITTED if
    070     * Tapestry is configured to provide a ValueEncoder automatically for the
    071     * type of property bound to the "value" parameter.
    072     */
    073    @Parameter(required = true, allowNull = false)
    074    private ValueEncoder encoder;
    075
    076    /**
    077     * The object that will perform input validation. The validate binding prefix is generally used to provide this
    078     * object in a declarative fashion.
    079     */
    080    @Parameter(defaultPrefix = BindingConstants.VALIDATE)
    081    @SuppressWarnings("unchecked")
    082    private FieldValidator<Object> validate;
    083
    084    @Inject
    085    private ComponentDefaultProvider defaultProvider;
    086
    087    @Inject
    088    private ComponentResources resources;
    089
    090    @Environmental
    091    private FormSupport formSupport;
    092
    093    @Inject
    094    private Environment environment;
    095
    096    @Inject
    097    private Request request;
    098
    099    @Environmental
    100    private ValidationTracker tracker;
    101
    102    @Inject
    103    private FieldValidationSupport fieldValidationSupport;
    104
    105    private String controlName;
    106
    107    String defaultLabel()
    108    {
    109        return defaultProvider.defaultLabel(resources);
    110    }
    111
    112    final ValueEncoder defaultEncoder()
    113    {
    114        return defaultProvider.defaultValueEncoder("value", resources);
    115    }
    116
    117    private static class Setup implements ComponentAction<RadioGroup>
    118    {
    119        private static final long serialVersionUID = -7984673040135949374L;
    120
    121        private final String controlName;
    122
    123        Setup(String controlName)
    124        {
    125            this.controlName = controlName;
    126        }
    127
    128        public void execute(RadioGroup component)
    129        {
    130            component.setup(controlName);
    131        }
    132
    133        @Override
    134        public String toString()
    135        {
    136            return String.format("RadioGroup.Setup[%s]", controlName);
    137        }
    138    }
    139
    140    private static final ComponentAction<RadioGroup> PROCESS_SUBMISSION = new ComponentAction<RadioGroup>()
    141    {
    142        private static final long serialVersionUID = -3857110108918776386L;
    143
    144        public void execute(RadioGroup component)
    145        {
    146            component.processSubmission();
    147        }
    148
    149        @Override
    150        public String toString()
    151        {
    152            return "RadioGroup.ProcessSubmission";
    153        }
    154    };
    155
    156    private void setup(String elementName)
    157    {
    158        controlName = elementName;
    159    }
    160
    161    private void processSubmission()
    162    {
    163        String rawValue = request.getParameter(controlName);
    164
    165        tracker.recordInput(this, rawValue);
    166        try
    167        {
    168            if (validate != null)
    169                fieldValidationSupport.validate(rawValue, resources, validate);
    170        }
    171        catch (ValidationException ex)
    172        {
    173            tracker.recordError(this, ex.getMessage());
    174        }
    175
    176        value = encoder.toValue(rawValue);
    177    }
    178
    179    /**
    180     * Obtains the element name for the group, and stores a {@link RadioContainer} into the {@link Environment} (so that
    181     * the {@link Radio} components can find it).
    182     */
    183    final void setupRender()
    184    {
    185        ComponentAction<RadioGroup> action = new Setup(formSupport.allocateControlName(clientId));
    186
    187        formSupport.storeAndExecute(this, action);
    188
    189        String submittedValue = tracker.getInput(this);
    190
    191        final String selectedValue = submittedValue != null ? submittedValue : encoder.toClient(value);
    192
    193        environment.push(RadioContainer.class, new RadioContainer()
    194        {
    195            public String getControlName()
    196            {
    197                return controlName;
    198            }
    199
    200            public boolean isDisabled()
    201            {
    202                return disabled;
    203            }
    204
    205            @SuppressWarnings("unchecked")
    206            public String toClient(Object value)
    207            {
    208                // TODO: Ensure that value is of the expected type?
    209
    210                return encoder.toClient(value);
    211            }
    212
    213            public boolean isSelected(Object value)
    214            {
    215                return TapestryInternalUtils.isEqual(encoder.toClient(value), selectedValue);
    216            }
    217        });
    218
    219        formSupport.store(this, PROCESS_SUBMISSION);
    220    }
    221
    222    /**
    223     * Pops the {@link RadioContainer} off the Environment.
    224     */
    225    final void afterRender()
    226    {
    227        environment.pop(RadioContainer.class);
    228    }
    229
    230    public String getControlName()
    231    {
    232        return controlName;
    233    }
    234
    235    public String getLabel()
    236    {
    237        return label;
    238    }
    239
    240    public boolean isDisabled()
    241    {
    242        return disabled;
    243    }
    244
    245    /**
    246     * Returns null; the radio group does not render as a tag and so doesn't have an id to share. RadioGroup implements
    247     * {@link org.apache.tapestry5.Field} only so it can interact with the
    248     * {@link org.apache.tapestry5.ValidationTracker}.
    249     *
    250     * @return null
    251     */
    252    public String getClientId()
    253    {
    254        return null;
    255    }
    256
    257    public boolean isRequired()
    258    {
    259        return validate.isRequired();
    260    }
    261}