blob: 053f056df6d2fbea22949e8728bbf6aad101edd2 [file] [log] [blame]
// Copyright 2007, 2008, 2009, 2011 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.apache.tapestry5.corelib.components;
import org.apache.tapestry5.*;
import org.apache.tapestry5.annotations.Environmental;
import org.apache.tapestry5.annotations.Events;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.internal.TapestryInternalUtils;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.services.TypeCoercer;
import org.apache.tapestry5.services.ComponentDefaultProvider;
import org.apache.tapestry5.services.Environment;
import org.apache.tapestry5.services.FormSupport;
import org.apache.tapestry5.services.Request;
/**
* A wrapper component around some number of {@link Radio} components, used to organize the selection and define the
* property to be edited. Examples of its use are in the {@link Radio} documentation.
*
* @tapestrydoc
*/
@Events(EventConstants.VALIDATE)
public class RadioGroup implements Field
{
/**
* The property read and updated by the group as a whole.
*/
@Parameter(required = true, principal = true, autoconnect = true)
private Object value;
/**
* If true, then the field will render out with a disabled attribute (to turn off client-side behavior). Further, a
* disabled field ignores any value in the request when the form is submitted.
*/
@Parameter("false")
private boolean disabled;
/**
* The user presentable label for the field. If not provided, a reasonable label is generated from the component's
* id, first by looking for a message key named "id-label" (substituting the component's actual id), then by
* converting the actual id to a presentable string (for example, "userId" to "User Id").
*/
@Parameter(defaultPrefix = BindingConstants.LITERAL)
private String label;
/**
* The id used to generate a page-unique client-side identifier for the component. If a component renders multiple
* times, a suffix will be appended to the to id to ensure uniqueness. The uniqued value may be accessed via the
* {@link #getClientId() clientId property}.
*/
@Parameter(value = "prop:componentResources.id", defaultPrefix = BindingConstants.LITERAL)
private String clientId;
/**
* A ValueEncoder used to convert server-side objects (provided by the
* selected Radio componnent's "value" parameter) into unique client-side
* strings (typically IDs) and back. Note: this parameter may be OMITTED if
* Tapestry is configured to provide a ValueEncoder automatically for the
* type of property bound to the "value" parameter.
*/
@Parameter(required = true, allowNull = false)
private ValueEncoder encoder;
/**
* The object that will perform input validation. The validate binding prefix is generally used to provide this
* object in a declarative fashion.
*/
@Parameter(defaultPrefix = BindingConstants.VALIDATE)
@SuppressWarnings("unchecked")
private FieldValidator<Object> validate;
@Inject
private ComponentDefaultProvider defaultProvider;
@Inject
private ComponentResources resources;
@Environmental
private FormSupport formSupport;
@Inject
private Environment environment;
@Inject
private Request request;
@Inject
private TypeCoercer typeCoercer;
@Environmental
private ValidationTracker tracker;
@Inject
private FieldValidationSupport fieldValidationSupport;
private String controlName;
String defaultLabel()
{
return defaultProvider.defaultLabel(resources);
}
final ValueEncoder defaultEncoder()
{
return defaultProvider.defaultValueEncoder("value", resources);
}
private static class Setup implements ComponentAction<RadioGroup>
{
private static final long serialVersionUID = -7984673040135949374L;
private final String controlName;
Setup(String controlName)
{
this.controlName = controlName;
}
public void execute(RadioGroup component)
{
component.setup(controlName);
}
@Override
public String toString()
{
return String.format("RadioGroup.Setup[%s]", controlName);
}
}
private static final ComponentAction<RadioGroup> PROCESS_SUBMISSION = new ComponentAction<RadioGroup>()
{
private static final long serialVersionUID = -3857110108918776386L;
public void execute(RadioGroup component)
{
component.processSubmission();
}
@Override
public String toString()
{
return "RadioGroup.ProcessSubmission";
}
};
private void setup(String elementName)
{
controlName = elementName;
}
private void processSubmission()
{
if (disabled)
{
return;
}
String rawValue = request.getParameter(controlName);
Object convertedValue = encoder.toValue(rawValue);
tracker.recordInput(this, rawValue);
try
{
if (validate != null)
fieldValidationSupport.validate(convertedValue, resources, validate);
}
catch (ValidationException ex)
{
tracker.recordError(this, ex.getMessage());
}
value = convertedValue;
}
/**
* Obtains the element name for the group, and stores a {@link RadioContainer} into the {@link Environment} (so that
* the {@link Radio} components can find it).
*/
final void setupRender()
{
ComponentAction<RadioGroup> action = new Setup(formSupport.allocateControlName(clientId));
formSupport.storeAndExecute(this, action);
String submittedValue = tracker.getInput(this);
final String selectedValue = submittedValue != null ? submittedValue : encoder.toClient(value);
final Class<?> boundType = resources.getBoundType("value");
environment.push(RadioContainer.class, new RadioContainer()
{
public String getControlName()
{
return controlName;
}
public boolean isDisabled()
{
return disabled;
}
private Object getObjectAsCorrectType(Object val)
{
if (val != null && boundType != null && !boundType.isAssignableFrom(val.getClass()))
{
return typeCoercer.coerce(val, boundType);
}
return val;
}
@SuppressWarnings("unchecked")
public String toClient(Object value)
{
return encoder.toClient(getObjectAsCorrectType(value));
}
public boolean isSelected(Object value)
{
return TapestryInternalUtils.isEqual(encoder.toClient(getObjectAsCorrectType(value)), selectedValue);
}
});
formSupport.store(this, PROCESS_SUBMISSION);
}
/**
* Pops the {@link RadioContainer} off the Environment.
*/
final void afterRender()
{
environment.pop(RadioContainer.class);
}
public String getControlName()
{
return controlName;
}
public String getLabel()
{
return label;
}
public boolean isDisabled()
{
return disabled;
}
/**
* Returns null; the radio group does not render as a tag and so doesn't have an id to share. RadioGroup implements
* {@link org.apache.tapestry5.Field} only so it can interact with the
* {@link org.apache.tapestry5.ValidationTracker}.
*
* @return null
*/
public String getClientId()
{
return null;
}
public boolean isRequired()
{
return validate.isRequired();
}
}