gwtupload.client.DecoratedFileUpload.java Source code

Java tutorial

Introduction

Here is the source code for gwtupload.client.DecoratedFileUpload.java

Source

/*
 * Copyright 2010 Manuel Carrasco Moino. (manolo at apache/org) 
 * http://code.google.com/p/gwtupload
 * 
 * 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 gwtupload.client;

import java.util.HashMap;
import java.util.List;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.*;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.FileUpload;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HasName;
import com.google.gwt.user.client.ui.HasText;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * A widget which wraps a FileUpload (native input file), hidding it
 * and replacing it by any clickable, customizable and stylable Widget.
 * 
 * To use it, first attach any HasClickHandlers widget to your display,
 * then wrap it with the DecoratedFileUpload.
 * 
 * <pre>
     
  Button myButton = new Button("Upload a file");
  RootPanel.get().add(myButton);
      
  DecoratedFileUpload d = new DecoratedFileUpload(myButton);
     
 * </pre>
 * 
 * To use it in UiBinder
 * 
 * <pre>
     
 <up:DecoratedFileUpload>
    <g:Button>Select a file ...</g:Button>
 </up:DecoratedFileUpload>
     
 * </pre>
 * 
 * CSS Rules:
 * 
 * <pre>
.DecoratedFileUpload {
  margin-right: 5px;
}
    
.DecoratedFileUpload-button {
  white-space: nowrap;
  font-size: 10px;
  min-height: 15px;
}
    
.DecoratedFileUpload .gwt-Anchor,
.DecoratedFileUpload .gwt-Label {
  color: blue;
  text-decoration: underline;
  cursor: pointer;
}
    
.DecoratedFileUpload-button:HOVER,
.DecoratedFileUpload .gwt-Button-over,
.DecoratedFileUpload .gwt-Anchor-over,
.DecoratedFileUpload .gwt-Label-over {
  color: #af6b29;
}
    
.DecoratedFileUpload-disabled .gwt-Button,
.DecoratedFileUpload-disabled .gwt-Anchor,
.DecoratedFileUpload-disabled .gwt-Label {
  color: grey;
}
 * </pre>
 * 
 * @author Manuel Carrasco Moino
 *
 */
public class DecoratedFileUpload extends FlowPanel implements HasName, HasChangeHandlers {

    /**
     * A FileUpload which implements onChange, onMouseOver and onMouseOut events.
     * 
     * Note: although FileUpload implements HasChangeHandlers and setEnabled in version Gwt 2.0.x,
     * we put it here in order to be compatible with older Gwt versions.
     *
     */
    public static class FileUploadWithMouseEvents extends MultipleFileUpload
            implements HasMouseOverHandlers, HasMouseOutHandlers, HasChangeHandlers {

        public HandlerRegistration addChangeHandler(ChangeHandler handler) {
            return addDomHandler(handler, ChangeEvent.getType());
        }

        public HandlerRegistration addMouseOutHandler(MouseOutHandler handler) {
            return addDomHandler(handler, MouseOutEvent.getType());
        }

        public HandlerRegistration addMouseOverHandler(final MouseOverHandler handler) {
            return addDomHandler(handler, MouseOverEvent.getType());
        }

        public boolean isEnabled() {
            return !getElement().getPropertyBoolean("disabled");
        }

        public void setEnabled(boolean enabled) {
            getElement().setPropertyBoolean("disabled", !enabled);
        }
    }

    /**
     * An abstract class which is the base for specific browser implementations.
     */
    private abstract static class DecoratedFileUploadImpl {

        protected Widget button;
        protected Panel container;
        protected FileUploadWithMouseEvents input;

        public void init(Panel container, FileUploadWithMouseEvents input) {
            this.container = container;
            this.input = input;
        }

        public void setButton(Widget widget) {
            this.button = widget;
            if (button instanceof HasMouseOverHandlers) {
                ((HasMouseOverHandlers) button).addMouseOverHandler(new MouseOverHandler() {
                    public void onMouseOver(MouseOverEvent event) {
                        button.addStyleName(STYLE_CLICKABLE_WIDGET + "-" + STYLE_BUTTON_OVER_SUFFIX);
                        container.addStyleDependentName(STYLE_BUTTON_OVER_SUFFIX);
                    }
                });
            }
            if (button instanceof HasMouseOutHandlers) {
                ((HasMouseOutHandlers) button).addMouseOutHandler(new MouseOutHandler() {
                    public void onMouseOut(MouseOutEvent event) {
                        button.removeStyleName(STYLE_CLICKABLE_WIDGET + "-" + STYLE_BUTTON_OVER_SUFFIX);
                        container.removeStyleDependentName(STYLE_BUTTON_OVER_SUFFIX);
                    }
                });
            }
        }

        public void onAttach() {
        }

        public void resize() {
        }
    }

    /**
     * Implementation for browsers which support the click() method:
     * IE, Chrome, Safari
     * 
     * The hack here is to put the customized button
     * and the file fileUplad statically positioned in an absolute panel. 
     * This panel has the size of the button, and the fileUplad is not shown 
     * because it is placed out of the width and height panel limits.
     * 
     */
    private static class DecoratedFileUploadImplClick extends DecoratedFileUploadImpl {

        private static HashMap<Widget, HandlerRegistration> clickHandlerCache = new HashMap<Widget, HandlerRegistration>();

        private static native void clickOnInputFile(Element elem) /*-{
                                                                  elem.click();
                                                                  }-*/;

        public void init(Panel container, FileUploadWithMouseEvents input) {
            super.init(container, input);
            container.add(input);
            DOM.setStyleAttribute(input.getElement(), "position", "fixed");
            DOM.setStyleAttribute(input.getElement(), "display", "inline");
            DOM.setStyleAttribute(input.getElement(), "top", "-1000px");
            DOM.setStyleAttribute(input.getElement(), "left", "-1000px");
        }

        public void setButton(Widget widget) {
            super.setButton(widget);
            HandlerRegistration clickRegistration = clickHandlerCache.get(widget);
            if (clickRegistration != null) {
                clickRegistration.removeHandler();
            }
            if (button != null) {
                if (button instanceof HasClickHandlers) {
                    clickRegistration = ((HasClickHandlers) button).addClickHandler(new ClickHandler() {
                        public void onClick(ClickEvent event) {
                            clickOnInputFile(input.getElement());
                        }
                    });
                    clickHandlerCache.put(widget, clickRegistration);
                }
            }
        }
    }

    /**
     * Implementation for IE6-8
     */
    @SuppressWarnings("unused")
    private static class DecoratedFileUploadImplIE extends DecoratedFileUploadImplClick {
        public void init(Panel container, FileUploadWithMouseEvents input) {
            super.init(container, input);
            DOM.setStyleAttribute(input.getElement(), "position", "absolute");
        }
    }

    /**
     * Implementation for browsers which do not support the click() method:
     * FF, Opera
     * 
     * The hack here is to place the customized button and the file fileUplad positioned
     * statically in an absolute panel which has size of the button. 
     * The file fileUplad is wrapped into a transparent panel, which also has the button
     * size and is placed covering the customizable button.
     * 
     * When the user puts his mouse over the button and clicks on it, what really 
     * happens is that the user clicks on the transparent file fileUplad showing
     * the choose file dialog.
     * 
     */
    @SuppressWarnings("unused")
    private static class DecoratedFileUploadImplNoClick extends DecoratedFileUploadImpl {

        private static final int DEFAULT_HEIGHT = 15;
        private static final int DEFAULT_WIDTH = 100;

        private SimplePanel wrapper;

        public void init(Panel container, FileUploadWithMouseEvents input) {
            super.init(container, input);
            wrapper = new SimplePanel();
            wrapper.add(input);
            container.add(wrapper);
            wrapper.setStyleName("wrapper");

            // Not using the GWT 2.0.x way to set Style attributes in order to be
            // compatible with old GWT releases
            DOM.setStyleAttribute(container.getElement(), "position", "relative");
            DOM.setStyleAttribute(container.getElement(), "overflow", "hidden");

            DOM.setStyleAttribute(wrapper.getElement(), "position", "absolute");
            DOM.setStyleAttribute(wrapper.getElement(), "textAlign", "left");
            DOM.setStyleAttribute(wrapper.getElement(), "zIndex", "1");
            DOM.setStyleAttribute(input.getElement(), "marginLeft", "-1500px");
            DOM.setStyleAttribute(input.getElement(), "fontSize", "350px");
            DOM.setStyleAttribute(input.getElement(), "borderWidth", "0px");
            DOM.setStyleAttribute(input.getElement(), "opacity", "0");
            DOM.setElementAttribute(input.getElement(), "size", "1");
            DOM.setElementAttribute(input.getElement(), "cursor", "pointer");

            // Trigger over and out handlers which already exist in the covered button.
            input.addMouseOverHandler(new MouseOverHandler() {
                public void onMouseOver(MouseOverEvent event) {
                    if (button != null) {
                        button.fireEvent(event);
                    }
                }
            });
            input.addMouseOutHandler(new MouseOutHandler() {
                public void onMouseOut(MouseOutEvent event) {
                    if (button != null) {
                        button.fireEvent(event);
                    }
                }
            });
        }

        public void onAttach() {
            if (width != 0 && height != 0) {
                container.setSize(width + "px", height + "px");
            } else {
                resize();
            }
            wrapper.setSize(width + "px", height + "px");
        }

        protected int width = 0, height = 0;

        // TODO: computed size
        public void resize() {
            if (button != null) {
                DOM.setStyleAttribute(button.getElement(), "position", "absolute");

                int w = button.getElement().getOffsetWidth();
                int h = button.getElement().getOffsetHeight();
                if (w <= 0) {
                    // Using old way for compatibility
                    String ws = DOM.getStyleAttribute(button.getElement(), "width");
                    if (ws != null) {
                        try {
                            w = Integer.parseInt(ws.replaceAll("[^\\d]", ""));
                        } catch (Exception e) {
                        }
                    }
                    if (w <= 0) {
                        w = DEFAULT_WIDTH;
                    } else {
                        width = w;
                    }
                }
                if (h <= 0) {
                    // Using old way for compatibility
                    String hs = DOM.getStyleAttribute(button.getElement(), "height");
                    if (hs != null) {
                        try {
                            h = Integer.parseInt(hs.replaceAll("[^\\d]", ""));
                        } catch (Exception e) {
                        }
                    }
                    if (h <= 0) {
                        h = DEFAULT_HEIGHT;
                    } else {
                        height = h;
                    }
                }
                container.setSize(w + "px", h + "px");
            }

            wrapper.setSize(width + "px", height + "px");
        }

        public void setSize(String width, String height) {
            button.setSize(width, height);
            container.setSize(width, height);
            wrapper.setSize(width, height);
        }
    }

    private static final String STYLE_BUTTON_OVER_SUFFIX = "over";
    private static final String STYLE_CONTAINER = "DecoratedFileUpload";
    private static final String STYLE_CLICKABLE_WIDGET = "DecoratedFileUpload-button";
    private static final String STYLE_DISABLED_SUFFIX = "disabled";
    protected Widget button;
    protected FileUploadWithMouseEvents input;;
    protected boolean reuseButton = false;
    private DecoratedFileUploadImpl impl;
    private String text = "";

    /**
     * Default constructor.
     */
    public DecoratedFileUpload() {
        this(null, null);
    }

    /**
     * Set the text when the element is attached. 
     */
    public DecoratedFileUpload(String text) {
        this((Widget) null);
        this.text = text;
    }

    /**
     * Constructor which uses the provided widget as the button where the
     * user has to click to show the browse file dialog.
     * The widget has to implement the HasClickHandlers interface.
     */
    public DecoratedFileUpload(Widget button) {
        this(button, null);
    }

    public DecoratedFileUpload(Widget button, FileUploadWithMouseEvents in) {
        impl = GWT.create(DecoratedFileUploadImpl.class);
        this.addStyleName(STYLE_CONTAINER);
        input = in;
        if (input == null) {
            input = new FileUploadWithMouseEvents();
        }
        impl.init(this, input);
        setButton(button);
    }

    /**
     * Add a handler which will be fired when the user selects a file.
     */
    public HandlerRegistration addChangeHandler(ChangeHandler handler) {
        return input.addChangeHandler(handler);
    }

    /**
     * Return the file name selected by the user.
     */
    public String getFilename() {
        return input.getFilename();
    }

    /**
     * Return the file names selected by the user.
     */
    public List<String> getFilenames() {
        return input.getFilenames();
    }

    /**
     * Return the original FileUpload wrapped by this decorated widget. 
     */
    public FileUpload getFileUpload() {
        return input;
    }

    /**
     * Return the name of the widget.
     */
    public String getName() {
        return input.getName();
    }

    /**
     * Return the text shown in the clickable button.
     */
    public String getText() {
        return text;
    }

    /**
     * Return this widget instance. 
     */
    public Widget getWidget() {
        return this;
    }

    /**
     * Return whether the fileUplad is enabled.
     */
    public boolean isEnabled() {
        return input.isEnabled();
    }

    /* (non-Javadoc)
     * @see com.google.gwt.user.client.ui.Composite#onAttach()
     */
    @Override
    public void onAttach() {
        super.onAttach();
        if (button == null) {
            button = new Label(text);
            setButton(button);
        }
        new Timer() {
            public void run() {
                impl.onAttach();
            }
        }.schedule(5);
    }

    /**
     * Set the button the user has to click on to show the browse dialog. 
     */
    public void setButton(Widget button) {
        if (button != null) {
            assert button instanceof HasClickHandlers : "Button should implement HasClickHandlers";
            if (this.button != null) {
                this.remove(this.button);
            }
            this.button = button;
            super.add(button);
            impl.setButton(button);
            button.addStyleName(STYLE_CLICKABLE_WIDGET);
            updateSize();
        }
    }

    /**
     * Set the button size.
     */
    public void setButtonSize(String width, String height) {
        button.setSize(width, height);
        updateSize();
    }

    /**
     * Enable or disable the FileInput. 
     */
    public void setEnabled(boolean b) {
        input.setEnabled(b);
        if (b) {
            this.removeStyleDependentName(STYLE_DISABLED_SUFFIX);
        } else {
            this.addStyleDependentName(STYLE_DISABLED_SUFFIX);
        }
    }

    /**
     * Set the widget name.
     */
    public void setName(String fieldName) {
        input.setName(fieldName);
    }

    /* (non-Javadoc)
     * @see com.google.gwt.user.client.ui.UIObject#setSize(java.lang.String, java.lang.String)
     */
    public void setSize(String width, String height) {
        setButtonSize(width, height);
    }

    /**
     * Set the text of the button.
     */
    public void setText(String text) {
        this.text = text;
        if (button instanceof HasText) {
            ((HasText) button).setText(text);
            updateSize();
        }
    }

    /**
     * Resize the absolute container to match the button size.
     */
    public void updateSize() {
        impl.resize();
    }

    public void enableMultiple(boolean b) {
        input.enableMultiple(b);
    }

    public void setAccept(String s) {
        input.setAccept(s);
    }

    @Override
    public void add(Widget widget) {
        if (widget instanceof HasClickHandlers) {
            setButton(widget);
        } else {
            super.add(widget);
        }
    }

}