package com.googlecode.tawus.components;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.tapestry5.Binding;
import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.EventConstants;
import org.apache.tapestry5.FieldValidationSupport;
import org.apache.tapestry5.FieldValidator;
import org.apache.tapestry5.Link;
import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.ValidationException;
import org.apache.tapestry5.ValidationTracker;
import org.apache.tapestry5.annotations.BeforeRenderTemplate;
import org.apache.tapestry5.annotations.Environmental;
import org.apache.tapestry5.annotations.Events;
import org.apache.tapestry5.annotations.Mixin;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.annotations.RequestParameter;
import org.apache.tapestry5.annotations.SupportsInformalParameters;
import org.apache.tapestry5.corelib.base.AbstractField;
import org.apache.tapestry5.corelib.data.BlankOption;
import org.apache.tapestry5.corelib.mixins.RenderDisabled;
import org.apache.tapestry5.internal.util.CaptureResultCallback;
import org.apache.tapestry5.ioc.Messages;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.internal.util.InternalUtils;
import org.apache.tapestry5.ioc.services.PropertyAccess;
import org.apache.tapestry5.ioc.services.PropertyAdapter;
import org.apache.tapestry5.ioc.services.TypeCoercer;
import org.apache.tapestry5.json.JSONObject;
import org.apache.tapestry5.services.ComponentDefaultProvider;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;
/**
* A simple implementation of select which takes a list and creates a select
* component from it. The content of <code><option></code> tag is supplied
* by the label parameter and the value by key parameter.
*
*/
@SupportsInformalParameters
@Events({ EventConstants.VALIDATE, EventConstants.VALUE_CHANGED + " when 'zone' parameter is 'bound'" })
public class SimpleSelect extends AbstractField
{
public static final String CHANGE_EVENT = "change";
@Parameter(required = true, allowNull = false)
private List<?> items;
@Parameter(defaultPrefix = BindingConstants.LITERAL, allowNull = false)
private String label;
@Parameter(defaultPrefix = BindingConstants.LITERAL, allowNull = false)
private String key;
@Parameter(autoconnect = true, required = true)
private Object value;
@Parameter(value = "auto", defaultPrefix = BindingConstants.LITERAL)
private BlankOption blankOption;
@Parameter(defaultPrefix = BindingConstants.LITERAL)
private String blankLabel;
@Parameter(defaultPrefix = BindingConstants.VALIDATE)
private FieldValidator<Object> validate;
@Parameter(defaultPrefix = BindingConstants.LITERAL)
private String zone;
@Inject
private Request request;
@Inject
private ComponentResources resources;
@Inject
private ComponentDefaultProvider defaultProvider;
@Environmental
private ValidationTracker tracker;
@Inject
private PropertyAccess propertyAccess;
@Inject
private TypeCoercer typeCoercer;
@Inject
private FieldValidationSupport fieldValidationSupport;
@Inject
private JavaScriptSupport javaScriptSupport;
@SuppressWarnings("unused")
@Mixin
private RenderDisabled renderDisabled;
private Map<String, PropertyAdapter> adapterMap;
private final static Pattern PROPERTY_PATTERN = Pattern.compile("#\\{([\\w.$_]+)\\}");
@Override
protected void processSubmission(String elementName)
{
String submittedValue = request.getParameter(elementName);
tracker.recordInput(this, submittedValue);
Object selectedValue = toValue(submittedValue);
putPropertyNameIntoBeanValidationContext("value");
try
{
fieldValidationSupport.validate(selectedValue, resources, validate);
value = selectedValue;
}
catch(ValidationException ex)
{
tracker.recordError(this, ex.getMessage());
}
removePropertyNameFromBeanValidationContext();
}
private Object toValue(String submittedValue)
{
if(InternalUtils.isBlank(submittedValue))
{
return null;
}
else
{
Object submittedKey = typeCoercer.coerce(submittedValue, getPropertyType(key));
for(Object item : items)
{
Object itemKey = getPropertyValue(item, key);
if(itemKey.equals(submittedKey))
{
return item;
}
}
return null;
}
}
Object onChange(@RequestParameter(value = "t:selectvalue", allowBlank = true) final String selectValue)
{
final Object newValue = toValue(selectValue);
CaptureResultCallback<Object> callback = new CaptureResultCallback<Object>();
this.resources.triggerEvent(EventConstants.VALUE_CHANGED, new Object[] { newValue }, callback);
this.value = newValue;
return callback.getResult();
}
void beginRender(MarkupWriter writer)
{
writer.element("select", "id", getClientId(), "name", getControlName());
putPropertyNameIntoBeanValidationContext("value");
validate.render(writer);
removePropertyNameFromBeanValidationContext();
resources.renderInformalParameters(writer);
decorateInsideField();
if(zone != null)
{
Link link = resources.createEventLink(CHANGE_EVENT);
JSONObject spec = new JSONObject("selectId", getClientId(), "zoneId", zone, "url", link.toURI());
javaScriptSupport.addInitializerCall("linkSelectToZone", spec);
}
}
void afterRender(MarkupWriter writer)
{
writer.end();
}
@BeforeRenderTemplate
void options(MarkupWriter writer)
{
if(showBlankOption())
{
writer.element("option", "value", "");
writer.write(blankLabel);
writer.end();
}
for(Object item : items)
{
writer.element("option", "value", getPropertyValue(item, key));
if(value != null && getPropertyValue(key).equals(getPropertyValue(item, key)))
{
writer.attributes("selected", "selected");
}
writer.write(getLabel(item));
writer.end();
}
}
private String getLabel(Object item)
{
if(label.contains("#"))
{
if(adapterMap == null)
{
adapterMap = new HashMap<String, PropertyAdapter>();
Matcher matcher = PROPERTY_PATTERN.matcher(label);
while(matcher.find())
{
adapterMap.put(matcher.group(0),
propertyAccess.getAdapter(resources.getBoundType("value")).getPropertyAdapter(matcher.group(1)));
}
}
String text = this.label;
for(String key : adapterMap.keySet())
{
String value = adapterMap.get(key) == null ? "" : adapterMap.get(key).get(item).toString();
text = text.replace(key, value);
}
return text;
}
else
{
Object propertyValue = getPropertyValue(item, label);
if(propertyValue == null)
{
return "";
}
else
{
return propertyValue.toString();
}
}
}
private boolean showBlankOption()
{
switch(blankOption)
{
case ALWAYS:
return true;
case NEVER:
return false;
default:
return !isRequired();
}
}
String defaultBlankLabel()
{
Messages containerMessages = resources.getContainerMessages();
String key = resources.getId() + "-blanklabel";
if(containerMessages.contains(key))
{
return containerMessages.get(key);
}
return null;
}
Binding defaultValidate()
{
return defaultProvider.defaultValidatorBinding("value", resources);
}
private Object getPropertyValue(String property)
{
return getPropertyValue(value, property);
}
private Object getPropertyValue(Object object, String property)
{
if(object == null)
{
return null;
}
return propertyAccess.getAdapter(object).getPropertyAdapter(property).get(object);
}
private Class<?> getPropertyType(String property)
{
return propertyAccess.getAdapter(resources.getBoundType("value")).getPropertyAdapter(property).getType();
}
}