/* * Copyright 2010 Manuel Carrasco Moñino. (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 Moñino * */ 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) { // Be compatible with UIBinder (#179), but avoid adding our own FileInput (#205) if (widget instanceof HasClickHandlers && !(widget instanceof FileUploadWithMouseEvents)) { setButton(widget); } else { super.add(widget); } } }