DgDpPalette.java (Drag and Dropo Tapestry Palette)
Version 0.2
// 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.RenderSupport;
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.IncludeJavaScriptLibrary;
import org.apache.tapestry5.annotations.IncludeStylesheet;
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.util.EnumSelectModel;
/**
*
* @author Pablo Henrique dos Reis
*
*/
@IncludeJavaScriptLibrary(value = {"DgDpPalette.js","${tapestry.scriptaculous}/dragdrop.js"})
@IncludeStylesheet(value = "DgDpPalette.css")
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;
@Environmental
private RenderSupport renderSupport;
@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);
}
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");
}
renderSupport.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) return;
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();
}
}
}DgDpPalette.tml(Template)
<div class="t-dgdppalette" xml:space="default" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
<div class="t-dgdppalette-available">
<div class="t-dgdppalette-title">
<t:delegate to="availableLabel"/>
</div>
<div id="${clientId}-available" class="t-dgdppalette-droppable" >
<t:loop source="AvailableOptions" value="row" volatile="false" t:encoder="encoder">
<div id="${idOption}" name="${idOption}"
class="${clientId}-draggable t-dgdppalette-option"
style="display: ${displayAvailable}; overflow: hidden;">
<img src="${imgAvailable}" alt="${message:imgAvailable-label}"
class="t-dgdppalette-available"/>
${row.label}
</div>
</t:loop>
</div>
</div>
<div class="t-dgdppalette-spacer">
</div>
<div class="t-dgdppalette-selected">
<div class="t-dgdppalette-title">
<t:delegate to="selectedLabel"/>
</div>
<div id="${clientId}-selected" class="t-dgdppalette-droppable-selected">
<t:loop source="AvailableOptions" value="row" volatile="false" t:encoder="encoder" >
<div id="selected-${idOption}" name="selected-${idOption}"
class="${clientId}-draggable t-dgdppalette-option" style="display: ${displaySelected}; overflow: hidden">
<img src="${imgSelected}" alt="${message:imgSeleted-label}"
class="t-dgdppalette-selected"/>
${row.label
<input type="hidden" value="${row.label}" id="label-${idOption}" />
<input type="hidden" value="${row.value}" id="value-${idOption}" />
</div>
</t:loop>
</div>
</div>
<input type="hidden" value="" id="${clientId}-status" name="${clientId}-status"/>
<div class="t-palette-spacer"/>
<script>
Droppables.add('${clientId}-available', {
accept: '${clientId}-draggable',
hoverclass: 'hover',
onDrop: function() {
$('${clientId}-status').value = false;
$('${clientId}-available').highlight();
}
});
Droppables.add('${clientId}-selected', {
accept: '${clientId}-draggable',
hoverclass: 'hover',
onDrop: function() {
$('${clientId}-status').value = true;
$('${clientId}-selected').highlight();
}
});
</script>
</div>DgDpPalette.js
// 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.
Tapestry.DgDpPalette = Class.create({
initialize : function(id, strings) {
this.id = id;
this.status = $(id + "-status");
this.boxavailable = $(id + "-available");
this.boxselected = $(id + "-selected");
this.selected = $(id);
strings.each(function (value)
{
this.registerDraggable(value);
}.bind(this));
strings.each(function (value)
{
//register element
this.registerDraggable("selected-" + value);
}.bind(this));
this.bindEvents();
},
bindEvents : function() {
},
registerDraggable : function(idDraggable) {
var idDropSelected = this.isDropSelected;
var setDropSelected = this.setDropSelected;
var selectedElement = this.isSelected;
var changeShow = this.changeShow;
var isDropInvalid = this.isDropInvalid;
var id = this.id;
new Draggable(idDraggable,{revert: true
,onStart: function(obj) {
var element = obj.element;
element.style.position = 'absolute';
element.style.zIndex = '1000';
}
,onEnd: function(obj) {
var element = obj.element;
var fieldSelected = idDropSelected(id);
var elementSelected = selectedElement(element.id);
var invalid = isDropInvalid(id);
element.style.position = 'relative';
element.style.zIndex = '25';
if(invalid) {
return;
}
var change = (fieldSelected && !elementSelected) ||
(!fieldSelected && elementSelected);
if(change) {
changeShow(element,id);
}
setDropSelected('',id);
}
} );
},
isDropInvalid : function(id) {
if($(id + "-status").value != 'true' && $(id + "-status").value != 'false' ) {
return true;
}
return false;
},
isDropSelected : function(id) {
if($(id + "-status").value == 'true') {
return true
}
return false;
},
setDropSelected : function(value, id) {
$(id + "-status").value = value;
},
isSelected : function(nome) {
return (nome.indexOf("selected") >= 0);
},
changeShow : function(element, id) {
var nome = element.id;
var outro;
var selected = nome.indexOf("selected") >= 0;
var valueSelect, labelSelect;
if(selected) {
outro = nome.substring(9);
valueSelect = "value-" + outro;
labelSelect = "label-" + outro
} else {
outro = "selected-" + nome;
valueSelect = "value-" + nome;
labelSelect = "label-" + nome;
}
outro = $(outro);
var cliente = $(id);
if(!selected) {
//add
var oOption = document.createElement("OPTION");
oOption.text= $(labelSelect).value;
oOption.value= $(valueSelect).value;
oOption.selected = true;
cliente.options[cliente.length] = oOption;
} else {
//remove
for( i = 0; i < cliente.length; i++) {
if(cliente[i].value == $(valueSelect).value) {
cliente[i] = null;
}
}
}
if(outro && element) {
if(element.style.display == 'none') {
element.style.display = '';
outro.style.display = 'none';
} else {
element.style.display = 'none';
outro.style.display = '';
}
}
}
});DgDpPalette.css
DIV.t-dgdppalette {
display: inline;
}
DIV.t-dgdppalette-title {
color: white;
background-color: #809FFF;
text-align: center;
font-weight: bold;
margin-bottom: 3px;
display: block;
}
DIV.t-dgdppalette-available {
float: left;
width: 15em;
z-index: 0;
}
DIV.t-dgdppalette-selected {
float: left;
width: 15em;
z-index: 0;
}
DIV.t-dgdppalette-spacer {
margin: 5px 5px;
float: left;
text-align: center;
}
DIV.t-dgdppalette-option {
z-index: 25;
min-width: 14em;
cursor: move;
background: none repeat scroll 0% 0% rgb(255, 255, 255);
border: 1px solid rgb(51, 51, 51);
}
DIV.t-dgdppalette-droppable {
min-height: 10em;
background: #fff;
border: 5px solid #ccc;
position: relative;
}
DIV.t-dgdppalette-droppable-selected {
min-height: 10em;
background: #fff;
border: 5px solid #ccc;
position: relative;
}
DIV.t-dgdppalette-droppable.hover {
border: 5px dashed #aaa;
background:#efefef;
}
DIV.t-dgdppalette-droppable-selected.hover {
border: 5px dashed #aaa;
background:#efefef;
}
IMG.t-dgdppalette-available {
height: 20px;
width: 20px;
}
IMG.t-dgdppalette-selected {
height: 20px;
width: 20px;
}Notes
- 0.1.0
- Created Tapestry5 componente Palette with Drag and Drop
- 0.2.0
- Modify definition of css using min-height and min-width