Wicket Drag and drop functionality for adding an image
Asked Answered
K

2

5

I'm making a wicket app that can manage some options for a cashdesk app. One of the options is to change the image of a selected Product.

The user(manager) can choose from the already present images in the database (SQL) when this option is selected, or add a new image if the desired image is not present. Don't mention the test names and awesome images (it's still in test-fase)Don't mention the test names and awesome images (it's still in test-fase)

I prefer to see the adding of an image achieved by Drag and Drop html5 demo [dnd-upload]
(From the desktop into the browser)

I'm currently using Wicket-6.2.0 and wicket-dnd 0.5.0 and i can't seem to get this working! All examples I can find are from wicket 2.x or lower.

It is possible to use drag and drop in Wicket-6.2, but how do I achieve this?

There seems to be some DraggableBehavior in wicket? Any help is welcome!

[UPDATE]

Upgraded to wicket-dnd 0.6

K answered 12/12, 2012 at 14:0 Comment(5)
isn´t wicked javascript?Epictetus
@KevinEsche No, but it uses Javascript to make ajax behavior.K
A new snapshot version of wicket-dnd which works with Wicket 6 has been announced today. Please try github.com/svenmeier/wicket-dndCarabiniere
I noticed wicket-dnd 0.6 drag and drop between any Wicket components Anybody knows if it's possible to drag and drop between desktop and browser?K
Made my own solution! I will add the answer tomorrow as i'm still busy tweaking (already works very nice)K
K
4

So here is the answer as promised! (only code but easy to understand if you are familiar with wicket)

The code makes it possible to drag a file to an area, and sends it to the wicket server (no matter what file it gets) this is not always what you want (but only what I need).

add the following javascript check in the drop.js if you only want 1 type of file to be uploaded:

// For each file: check if files are images
for (i = 0; i < files.length; i++) {
    if (!files[i].type.match('image.*')) { // Replace with what you need
        $('#dropAppearance p').html('Hey! Images only');
        return false;
    }
}

files:
- MyPage.java
- MyPage.html
- DropZone.java
- DropZone.html
- DropZone.properties
- DropAjaxBehavior
- drop.js
- drop.css

Used Libs:
- jQuery.js
- jQuery-ui.js
- wicket 6.2
- slf4j-1.2.16
- log4j-1.2.16
- guava-13.0.1

I did not included the imports because, i'm lazy

MyPage.java

public final class MyPage extends Page {

    /**
     * Constructor
     */
    public HomePage() {
    }

    @Override
    public void onInitialize() {
        super.onInitialize();
        add(new DropZone("dropZone", 300, 200));
    }
}

MyPage.html

<!DOCTYPE html>
<html lang="en">
<body>
    <wicket:extend>
        // The following line adds a DropZone
        <div wicket:id="dropZone"></div>
    </wicket:extend>
</body>
</html>

DropZone.java

public class DropZone extends Panel {
    private static final ResourceReference JS_DROP = new JavaScriptResourceReference(DropZone.class, "drop.js");
    private static final ResourceReference CSS_DROP = new CssResourceReference(DropZone.class, "drop.css");
    private static final ResourceReference JQUERY = new JavaScriptResourceReference(DropZone.class, "jQuery.js");
    private static final ResourceReference JQUERY_UI = new JavaScriptResourceReference(DropZone.class, "jQuery-ui.js");

    private static final String ID_DROPZONE = "drop-container";

    /**
     * Constructor
     * 
     * @param id String The component id
     * @param height int The height of the DropZone component [in pixels]
     * @param width int The width of the DropZone component [in pixels]
     */
    public DropZone(String id, int width, int height) {
        super(id);
        final WebMarkupContainer dropZone = new WebMarkupContainer(ID_DROPZONE);
        final DropAjaxBehavior dropAjaxBehavior = new DropAjaxBehavior();

        dropZone.add(dropAjaxBehavior);
        dropZone.add(new AttributeModifier("style", new Model<String>("width:" + width + "px;height:" + height + "px;")));

        add(dropZone);
    }

    @Override
    public final void renderHead(IHeaderResponse response) {
        super.renderHead(response);
        // Important to add jQuery before own javascript
        response.render(JavaScriptHeaderItem.forReference(JQUERY));
        response.render(JavaScriptHeaderItem.forReference(JQUERY_UI));
        response.render(JavaScriptHeaderItem.forReference(JS_DROP));
        response.render(CssContentHeaderItem.forReference(CSS_DROP));
    }
}

DropZone.html

<html>
<body>
    <wicket:panel>
        <div wicket:id="drop-container" id="dropContainer">
            <div id="dropAppearance">
                <p>
                    <wicket:message key="drop-message">[DROPZONE MESSAGE]</wicket:message>
                </p>
            </div>
        </div>
    </wicket:panel>
</body>

DropZone.properties

drop-message = Drop Files Here

DropAjaxBehavior.java

public class DropAjaxBehavior extends AbstractAjaxBehavior {
    private static final Logger LOG = LoggerFactory.getLogger(DropAjaxBehavior.class);

    @Override
    public final void onRequest() {
        LOG.debug("Received request");

        final RequestCycle requestCycle = RequestCycle.get();

        processRequest(requestCycle);
        sendResponse(requestCycle);
    }

    private void processRequest(RequestCycle requestCycle) {

        final WebRequest wr = (WebRequest)requestCycle.getRequest();
        final HttpServletRequest hsr = (HttpServletRequest)wr.getContainerRequest();

        try {
            final byte[] data = new byte[hsr.getContentLength()];
            ByteStreams.readFully(hsr.getInputStream(), data);

            // filename:<NAME>;data:<TYPE>;base64,<FILEDATA>
            final String[] base64Data = new String(data).split(";");
            final String fileName = base64Data[0].substring(base64Data[0].indexOf(':') + 1, base64Data[0].length());
            final String dataType = base64Data[1].substring(base64Data[1].indexOf(':') + 1, base64Data[1].length());
            final String binaryData = base64Data[2].substring(base64Data[2].indexOf(',') + 1, base64Data[2].length());

            // [in my case] do something if the fileType is an image
            if (dataType.contains("image")) {
                final byte[] image = DatatypeConverter.parseBase64Binary(binaryData);
                DatabaseQuery.addImage(image, fileName);
            }
            // But you can make a local file
            // final File file = new File(fileName);
            // final ByteArrayInputStream binaryInputstream = new ByteArrayInputStream(image);
            // final FileOutputStream outputStream = new FileOutputStream(file);
            // ByteStreams.copy(binaryInputstream, outputStream);
            // outputStream.close();
        } catch (IOException ioe) {
            LOG.error("IO error while reading HttpServletRequest: ", ioe);
        }
    }

    private void sendResponse(RequestCycle requestCycle) {
        // Just some response
        requestCycle.scheduleRequestHandlerAfterCurrent(new TextRequestHandler("text/html", "UTF-8", "done"));
    }

    @Override
    protected final void onComponentTag(ComponentTag tag) {
        tag.put("my:dropcontainer.callback", getCallbackUrl().toString());
    }
}

drop.css

#dropContainer {
    background-color: #FFFFFF;
    border: 4px dashed #C9C9C9;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
    position: absolute;
}

#dropAppearance {
    height: 100%;
    width: 100%;
    display: table;
    box-sizing: border-box;
}

#dropAppearance p {
    display: table-cell;
    vertical-align: middle;
    text-align: center;
    font-size: 2em;
    color: #797979;
}

drop.js

$(document).ready(
        function() {
            // Makes sure the dataTransfer information is sent when we
            // Drop the item in the drop box.
            jQuery.event.props.push('dataTransfer');

            // As far as i know Firefox needs to cancel this event (otherwise it
            // opens
            // dropped files in the browser)
            $('#dropContainer').attr('ondragover', "return false");

            $('#dropContainer').bind(
                    'drop',
                    function(e) {
                        // Files that have been dragged into the drop area
                        var files = e.dataTransfer.files;

                        // Upload each file
                        $.each(files, function(i, file) {
                            var reader = new FileReader();

                            reader.onload = function(input) {
                                var fileName = "fileName:" + file.name + ";";
                                var base64data = input.target.result;

                                $.ajax({
                                    url : $('#dropContainer').attr(
                                            'my:dropcontainer.callback'),
                                    type : 'post',
                                    cache : false,
                                    // Add date before raw base64 file data
                                    data : fileName + base64data,
                                    processData : false,
                                    contentType : false,
                                });
                            };

                            // decode into base64
                            reader.readAsDataURL(file);
                        });
                        return false;
                    });

            // Using little dragging hack because of the HTML5 spec problem
            // URL:
            // http://www.quirksmode.org/blog/archives/2009/09/the_html5_drag.html
            // works like:

            // <parent element>
            // dragging = 0
            // <drop_container>
            // dragging = 1
            // <child>
            // dragging = 2
            // </child>
            // dragging = 1
            // </drop_container>
            // dragging = 0
            // </parent element>

            var dragging = 0;
            $('#dropContainer').bind('dragenter', function() {
                dragging++;
                setHoverDropContainer();
                return false;
            });

            $('#dropContainer').bind('dragleave', function() {
                dragging--;
                if (dragging === 0) {
                    resetHoverDropContainer();
                }
                return false;
            });

            $('#dropContainer').bind('drop', function() {
                dragging = 0; // reset dragging hack
                resetHoverDropContainer();
                return false;
            });
        });

    function setHoverDropContainer() {
        // change colors with smooth transition
        setCSS('#dropContainer', {
            'border-color' : '#0000FF',
            'background-color' : '#EDF4FE',
            '-webkit-transition' : 'background-color 0.6s ease',
            '-moz-transition' : 'background-color 0.6s ease',
            '-o-transition' : 'background-color 0.6s ease',
            'transition' : 'background-color 0.6s ease',
            '-webkit-transition' : 'border-color 0.6s ease',
            '-moz-transition' : 'border-color 0.6s ease',
            '-o-transition' : 'border-color 0.6s ease',
            'transition' : 'border-color 0.6s ease'
        });
    }

    function resetHoverDropContainer() {
        // change colors with smooth transition
        setCSS('#dropContainer', {
            'border-color' : '#C9C9C9',
            'background-color' : '#FFFFFF',
            '-webkit-transition' : 'background-color 0.6s ease',
            '-moz-transition' : 'background-color 0.6s ease',
            '-o-transition' : 'background-color 0.6s ease',
            'transition' : 'background-color 0.6s ease',
            '-webkit-transition' : 'border-color 0.6s ease',
            '-moz-transition' : 'border-color 0.6s ease',
            '-o-transition' : 'border-color 0.6s ease',
            'transition' : 'border-color 0.6s ease'
        });
    }

    function setCSS(element, values) {
        $(element).css(values);
    }
}
K answered 19/12, 2012 at 9:33 Comment(0)
A
5

Based on your code I wrote my custom Ajax behavior. I simplified it however, it is working for me without dragging hack.

AbstractFileDropAjaxBehavior.java

public abstract class AbstractFileDropAjaxBehavior extends AbstractDefaultAjaxBehavior
{

    @Override
    protected void respond(final AjaxRequestTarget target)
    {
        RequestCycle requestCycle = RequestCycle.get();

        StringValue data = requestCycle.getRequest().getRequestParameters().getParameterValue("data");
        String[] base64Data = data.toString().split(";");
        String fileName = base64Data[0].substring(base64Data[0].indexOf(':') + 1, base64Data[0].length());
        String dataType = base64Data[1].substring(base64Data[1].indexOf(':') + 1, base64Data[1].length());
        String binaryData = base64Data[2].substring(base64Data[2].indexOf(',') + 1, base64Data[2].length());
        byte[] rawData = DatatypeConverter.parseBase64Binary(binaryData);

        processFile(fileName, dataType, rawData);
    }

    @Override
    protected void onComponentTag(ComponentTag tag) {
        tag.put("my:dropcontainer.callback", getCallbackUrl().toString());

        tag.put("ondragover", "return false;");
        tag.put("ondrop", "return AbstractFileDropAjaxBehavior_upload(event, '#" + tag.getId() + "');");
        tag.put("ondragenter", "$('#" + tag.getId() + "').addClass('dropover')");
        tag.put("ondragleave", "$('#" + tag.getId() + "').removeClass('dropover')");
    }

    @Override
    public void renderHead(Component component, IHeaderResponse response) {
        super.renderHead(component, response);

        response.render(OnDomReadyHeaderItem.forScript("jQuery.event.props.push('dataTransfer');"));

        response.render(JavaScriptContentHeaderItem.forScript("function AbstractFileDropAjaxBehavior_upload(e, selector) {\n" + 
                "   var files = e.dataTransfer.files;\n" + 
                "   \n" + 
                "   $.each(files, function(i, file) {\n" + 
                "       var reader = new FileReader();\n" + 
                "       \n" + 
                "       reader.onload = function(input) {\n" + 
                "           var fileName = \"fileName:\" + file.name + \";\";\n" + 
                "           var base64data = input.target.result;\n" + 
                "           \n" + 
                "           Wicket.Ajax.post({\n" + 
                "               \"u\": $(selector).attr('my:dropcontainer.callback'),\n" + 
                "               \"ep\": {\n" + 
                "                   \"data\" : fileName + base64data,\n" + 
                "               },\n" + 
                "           });\n" + 
                "           \n" + 
                "       };\n" + 
                "\n" + 
                "       reader.readAsDataURL(file);\n" + 
                "   });\n" + 
                "   \n" + 
                "   $(selector).removeClass('dropover');\n" + 
                "   \n" + 
                "   return false;   \n" + 
                "}\n", "AbstractFileDropAjaxBehavior-script"));
    }

    protected abstract void processFile(String fileName, String dataType, byte[] rawData);

}
Ankus answered 15/11, 2013 at 11:49 Comment(0)
K
4

So here is the answer as promised! (only code but easy to understand if you are familiar with wicket)

The code makes it possible to drag a file to an area, and sends it to the wicket server (no matter what file it gets) this is not always what you want (but only what I need).

add the following javascript check in the drop.js if you only want 1 type of file to be uploaded:

// For each file: check if files are images
for (i = 0; i < files.length; i++) {
    if (!files[i].type.match('image.*')) { // Replace with what you need
        $('#dropAppearance p').html('Hey! Images only');
        return false;
    }
}

files:
- MyPage.java
- MyPage.html
- DropZone.java
- DropZone.html
- DropZone.properties
- DropAjaxBehavior
- drop.js
- drop.css

Used Libs:
- jQuery.js
- jQuery-ui.js
- wicket 6.2
- slf4j-1.2.16
- log4j-1.2.16
- guava-13.0.1

I did not included the imports because, i'm lazy

MyPage.java

public final class MyPage extends Page {

    /**
     * Constructor
     */
    public HomePage() {
    }

    @Override
    public void onInitialize() {
        super.onInitialize();
        add(new DropZone("dropZone", 300, 200));
    }
}

MyPage.html

<!DOCTYPE html>
<html lang="en">
<body>
    <wicket:extend>
        // The following line adds a DropZone
        <div wicket:id="dropZone"></div>
    </wicket:extend>
</body>
</html>

DropZone.java

public class DropZone extends Panel {
    private static final ResourceReference JS_DROP = new JavaScriptResourceReference(DropZone.class, "drop.js");
    private static final ResourceReference CSS_DROP = new CssResourceReference(DropZone.class, "drop.css");
    private static final ResourceReference JQUERY = new JavaScriptResourceReference(DropZone.class, "jQuery.js");
    private static final ResourceReference JQUERY_UI = new JavaScriptResourceReference(DropZone.class, "jQuery-ui.js");

    private static final String ID_DROPZONE = "drop-container";

    /**
     * Constructor
     * 
     * @param id String The component id
     * @param height int The height of the DropZone component [in pixels]
     * @param width int The width of the DropZone component [in pixels]
     */
    public DropZone(String id, int width, int height) {
        super(id);
        final WebMarkupContainer dropZone = new WebMarkupContainer(ID_DROPZONE);
        final DropAjaxBehavior dropAjaxBehavior = new DropAjaxBehavior();

        dropZone.add(dropAjaxBehavior);
        dropZone.add(new AttributeModifier("style", new Model<String>("width:" + width + "px;height:" + height + "px;")));

        add(dropZone);
    }

    @Override
    public final void renderHead(IHeaderResponse response) {
        super.renderHead(response);
        // Important to add jQuery before own javascript
        response.render(JavaScriptHeaderItem.forReference(JQUERY));
        response.render(JavaScriptHeaderItem.forReference(JQUERY_UI));
        response.render(JavaScriptHeaderItem.forReference(JS_DROP));
        response.render(CssContentHeaderItem.forReference(CSS_DROP));
    }
}

DropZone.html

<html>
<body>
    <wicket:panel>
        <div wicket:id="drop-container" id="dropContainer">
            <div id="dropAppearance">
                <p>
                    <wicket:message key="drop-message">[DROPZONE MESSAGE]</wicket:message>
                </p>
            </div>
        </div>
    </wicket:panel>
</body>

DropZone.properties

drop-message = Drop Files Here

DropAjaxBehavior.java

public class DropAjaxBehavior extends AbstractAjaxBehavior {
    private static final Logger LOG = LoggerFactory.getLogger(DropAjaxBehavior.class);

    @Override
    public final void onRequest() {
        LOG.debug("Received request");

        final RequestCycle requestCycle = RequestCycle.get();

        processRequest(requestCycle);
        sendResponse(requestCycle);
    }

    private void processRequest(RequestCycle requestCycle) {

        final WebRequest wr = (WebRequest)requestCycle.getRequest();
        final HttpServletRequest hsr = (HttpServletRequest)wr.getContainerRequest();

        try {
            final byte[] data = new byte[hsr.getContentLength()];
            ByteStreams.readFully(hsr.getInputStream(), data);

            // filename:<NAME>;data:<TYPE>;base64,<FILEDATA>
            final String[] base64Data = new String(data).split(";");
            final String fileName = base64Data[0].substring(base64Data[0].indexOf(':') + 1, base64Data[0].length());
            final String dataType = base64Data[1].substring(base64Data[1].indexOf(':') + 1, base64Data[1].length());
            final String binaryData = base64Data[2].substring(base64Data[2].indexOf(',') + 1, base64Data[2].length());

            // [in my case] do something if the fileType is an image
            if (dataType.contains("image")) {
                final byte[] image = DatatypeConverter.parseBase64Binary(binaryData);
                DatabaseQuery.addImage(image, fileName);
            }
            // But you can make a local file
            // final File file = new File(fileName);
            // final ByteArrayInputStream binaryInputstream = new ByteArrayInputStream(image);
            // final FileOutputStream outputStream = new FileOutputStream(file);
            // ByteStreams.copy(binaryInputstream, outputStream);
            // outputStream.close();
        } catch (IOException ioe) {
            LOG.error("IO error while reading HttpServletRequest: ", ioe);
        }
    }

    private void sendResponse(RequestCycle requestCycle) {
        // Just some response
        requestCycle.scheduleRequestHandlerAfterCurrent(new TextRequestHandler("text/html", "UTF-8", "done"));
    }

    @Override
    protected final void onComponentTag(ComponentTag tag) {
        tag.put("my:dropcontainer.callback", getCallbackUrl().toString());
    }
}

drop.css

#dropContainer {
    background-color: #FFFFFF;
    border: 4px dashed #C9C9C9;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
    position: absolute;
}

#dropAppearance {
    height: 100%;
    width: 100%;
    display: table;
    box-sizing: border-box;
}

#dropAppearance p {
    display: table-cell;
    vertical-align: middle;
    text-align: center;
    font-size: 2em;
    color: #797979;
}

drop.js

$(document).ready(
        function() {
            // Makes sure the dataTransfer information is sent when we
            // Drop the item in the drop box.
            jQuery.event.props.push('dataTransfer');

            // As far as i know Firefox needs to cancel this event (otherwise it
            // opens
            // dropped files in the browser)
            $('#dropContainer').attr('ondragover', "return false");

            $('#dropContainer').bind(
                    'drop',
                    function(e) {
                        // Files that have been dragged into the drop area
                        var files = e.dataTransfer.files;

                        // Upload each file
                        $.each(files, function(i, file) {
                            var reader = new FileReader();

                            reader.onload = function(input) {
                                var fileName = "fileName:" + file.name + ";";
                                var base64data = input.target.result;

                                $.ajax({
                                    url : $('#dropContainer').attr(
                                            'my:dropcontainer.callback'),
                                    type : 'post',
                                    cache : false,
                                    // Add date before raw base64 file data
                                    data : fileName + base64data,
                                    processData : false,
                                    contentType : false,
                                });
                            };

                            // decode into base64
                            reader.readAsDataURL(file);
                        });
                        return false;
                    });

            // Using little dragging hack because of the HTML5 spec problem
            // URL:
            // http://www.quirksmode.org/blog/archives/2009/09/the_html5_drag.html
            // works like:

            // <parent element>
            // dragging = 0
            // <drop_container>
            // dragging = 1
            // <child>
            // dragging = 2
            // </child>
            // dragging = 1
            // </drop_container>
            // dragging = 0
            // </parent element>

            var dragging = 0;
            $('#dropContainer').bind('dragenter', function() {
                dragging++;
                setHoverDropContainer();
                return false;
            });

            $('#dropContainer').bind('dragleave', function() {
                dragging--;
                if (dragging === 0) {
                    resetHoverDropContainer();
                }
                return false;
            });

            $('#dropContainer').bind('drop', function() {
                dragging = 0; // reset dragging hack
                resetHoverDropContainer();
                return false;
            });
        });

    function setHoverDropContainer() {
        // change colors with smooth transition
        setCSS('#dropContainer', {
            'border-color' : '#0000FF',
            'background-color' : '#EDF4FE',
            '-webkit-transition' : 'background-color 0.6s ease',
            '-moz-transition' : 'background-color 0.6s ease',
            '-o-transition' : 'background-color 0.6s ease',
            'transition' : 'background-color 0.6s ease',
            '-webkit-transition' : 'border-color 0.6s ease',
            '-moz-transition' : 'border-color 0.6s ease',
            '-o-transition' : 'border-color 0.6s ease',
            'transition' : 'border-color 0.6s ease'
        });
    }

    function resetHoverDropContainer() {
        // change colors with smooth transition
        setCSS('#dropContainer', {
            'border-color' : '#C9C9C9',
            'background-color' : '#FFFFFF',
            '-webkit-transition' : 'background-color 0.6s ease',
            '-moz-transition' : 'background-color 0.6s ease',
            '-o-transition' : 'background-color 0.6s ease',
            'transition' : 'background-color 0.6s ease',
            '-webkit-transition' : 'border-color 0.6s ease',
            '-moz-transition' : 'border-color 0.6s ease',
            '-o-transition' : 'border-color 0.6s ease',
            'transition' : 'border-color 0.6s ease'
        });
    }

    function setCSS(element, values) {
        $(element).css(values);
    }
}
K answered 19/12, 2012 at 9:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.