// Copyright 2010 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 com.google.gsoc.web.components;
import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newSet;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.tapestry5.Asset;
import org.apache.tapestry5.Binding;
import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.Block;
import org.apache.tapestry5.CSSClassConstants;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.FieldValidationSupport;
import org.apache.tapestry5.FieldValidator;
import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.OptionGroupModel;
import org.apache.tapestry5.OptionModel;
import org.apache.tapestry5.SelectModel;
import org.apache.tapestry5.SelectModelVisitor;
import org.apache.tapestry5.ValidationException;
import org.apache.tapestry5.ValidationTracker;
import org.apache.tapestry5.ValueEncoder;
import org.apache.tapestry5.annotations.Environmental;
import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.corelib.base.AbstractField;
import org.apache.tapestry5.internal.util.SelectModelRenderer;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
import org.apache.tapestry5.json.JSONArray;
import org.apache.tapestry5.services.ComponentDefaultProvider;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;
import org.apache.tapestry5.util.EnumSelectModel;
/**
*
* @author Pablo Henrique dos Reis
*
*/
@Import(stylesheet = "DgDpPalette.css", library = {"DgDpPalette.js","${tapestry.scriptaculous}/dragdrop.js"})
public class DgDpPalette extends AbstractField
{
private Map<Object, OptionModel> valueToOptionModel;
private static Map<String, OptionModel> stringToOptionModel;
private SelectModelRenderer renderer;
private List<Runnable> availableOptions;
private List<OptionModel> selectedOptions;
@Parameter(required = true, allowNull = false)
private List<Object> selected;
@Property
private OptionModel row;
@Parameter(required = true, allowNull = false)
private SelectModel model;
@Parameter(required = true, allowNull = false)
private ValueEncoder<Object> encoder;
@SuppressWarnings("unused")
@Property(write = false)
@Parameter(required = true, allowNull = false, value = "message:available-label",
defaultPrefix = BindingConstants.LITERAL)
private Block availableLabel;
@SuppressWarnings("unused")
@Property(write = false)
@Parameter(required = true, allowNull = false, value = "message:selected-label",
defaultPrefix = BindingConstants.LITERAL)
private Block selectedLabel;
@SuppressWarnings("unused")
@Parameter(value = "asset:available.png")
@Property(write = false)
private Asset imgAvailable;
@SuppressWarnings("unused")
@Parameter(value = "asset:selected.png")
@Property(write = false)
private Asset imgSelected;
@Inject
private JavaScriptSupport javascriptSupport;
@Environmental
private ValidationTracker tracker;
@Inject
private ComponentDefaultProvider defaultProvider;
@Inject
private ComponentResources componentResources;
@Inject
private FieldValidationSupport fieldValidationSupport;
@Parameter(defaultPrefix = BindingConstants.VALIDATE)
private FieldValidator<Object> validate;
/**
* Needed to access query parameters when processing form submission.
*/
@Inject
private Request request;
public List<Object> getSelected()
{
if(selected == null)
return Collections.emptyList();
return selected;
}
public List<OptionModel> getAvailableOptions()
{
return model.getOptions();
}
public boolean isRowSelected()
{
return selectedOptions.contains(row);
}
public String getDisplayAvailable()
{
return isRowSelected() ? CSSClassConstants.INVISIBLE : "";
}
public String getDisplaySelected()
{
return isRowSelected() ? "" : CSSClassConstants.INVISIBLE;
}
public String getIdOption()
{
return row.getValue() + "-" + getClientId() + "-option";
}
public String getRowClient()
{
return toClient(row.getValue());
}
String toClient(Object value)
{
return encoder.toClient(value);
}
/**
* Prevent the body from rendering.
*/
boolean beforeRenderBody()
{
return false;
}
private final class OptionGroupStart implements Runnable
{
private final OptionGroupModel model;
private OptionGroupStart(OptionGroupModel model)
{
this.model = model;
}
public void run()
{
renderer.beginOptionGroup(model);
}
}
private final class OptionGroupEnd implements Runnable
{
private final OptionGroupModel model;
private OptionGroupEnd(OptionGroupModel model)
{
this.model = model;
}
public void run()
{
renderer.endOptionGroup(model);
}
}
private final class RenderOption implements Runnable
{
private final OptionModel model;
private RenderOption(OptionModel model)
{
this.model = model;
}
public void run()
{
renderer.option(model);
}
}
private static ValueEncoder<OptionModel> optionEncoder = new ValueEncoder<OptionModel>()
{
public String toClient(OptionModel value)
{
return value.getLabel();
}
public OptionModel toValue(String clientValue)
{
return stringToOptionModel.get(clientValue);
}
};
public ValueEncoder<OptionModel> getEncoder()
{
return optionEncoder;
}
@SuppressWarnings("unchecked")
void setupRender(MarkupWriter writer)
{
valueToOptionModel = CollectionFactory.newMap();
stringToOptionModel = CollectionFactory.newMap();
availableOptions = CollectionFactory.newList();
selectedOptions = CollectionFactory.newList();
renderer = new SelectModelRenderer(writer, encoder);
final Set selectedSet = newSet(getSelected());
SelectModelVisitor visitor = new SelectModelVisitor()
{
public void beginOptionGroup(OptionGroupModel groupModel)
{
availableOptions.add(new OptionGroupStart(groupModel));
}
public void endOptionGroup(OptionGroupModel groupModel)
{
availableOptions.add(new OptionGroupEnd(groupModel));
}
public void option(OptionModel optionModel)
{
Object value = optionModel.getValue();
boolean isSelected = selectedSet.contains(value);
if (isSelected)
{
selectedOptions.add(optionModel);
valueToOptionModel.put(value, optionModel);
return;
}
stringToOptionModel.put(optionModel.getLabel(), optionModel);
availableOptions.add(new RenderOption(optionModel));
}
};
model.visit(visitor);
}
public void beginRender(MarkupWriter writer)
{
JSONArray selectedValues = new JSONArray();
for (OptionModel selected : selectedOptions)
{
Object value = selected.getValue();
String clientValue = encoder.toClient(value);
selectedValues.put(clientValue);
}
String clientId = getClientId();
writer.element("Select",
"multiple","multiple",
"id", clientId,
"class", CSSClassConstants.INVISIBLE,
"name", getControlName());
for(Object object: getSelected())
{
OptionModel model = valueToOptionModel.get(object);
writer.element("Option",
"value",model.getValue(),
"selected", "selected");
writer.write(model.getLabel());
writer.end();
}
writer.end();
String script = "new Tapestry.DgDpPalette('%s', %s)";
JSONArray array = new JSONArray();
for(OptionModel option : model.getOptions())
{
array.put(option.getValue() + "-" + getClientId() + "-option");
}
javascriptSupport.addScript(script,clientId, array);
}
Binding defaultValidate()
{
return this.defaultProvider.defaultValidatorBinding("selected", this.componentResources);
}
@SuppressWarnings("unchecked")
ValueEncoder defaultEncoder()
{
return defaultProvider.defaultValueEncoder("value", this.componentResources);
}
@SuppressWarnings("unchecked")
SelectModel defaultModel()
{
Class valueType = this.componentResources.getBoundType("value");
if (valueType == null)
return null;
if (Enum.class.isAssignableFrom(valueType))
return new EnumSelectModel(valueType, this.componentResources.getContainerMessages());
return null;
}
@Override
protected void processSubmission(String elementName)
{
List<Object> selected = getSelected();
selected.clear();
String[] parameters = request.getParameters(elementName);
this.tracker.recordInput(this,parameters != null ? parameters.toString() : null);
if(parameters != null)
{
for (String string : parameters)
{
Object object = encoder.toValue(string);
selected.add(object);
}
}
putPropertyNameIntoBeanValidationContext("selected");
try
{
this.fieldValidationSupport.validate(selected, this.componentResources, this.validate);
this.selected = selected;
}
catch (final ValidationException e)
{
this.tracker.recordError(this, e.getMessage());
}
removePropertyNameFromBeanValidationContext();
}
}