Home

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

PabloGSOC2010DgDpPalette (last edited 2010-07-12 16:50:31 by PabloGSOC2010)