From 8cdaeb241a7e57561e69c2ee46d0dde54e591c45 Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Thu, 6 Oct 2016 16:19:53 +0300 Subject: Add Upload to compatibility packages Step 1 / 2 of adding a new Upload Component. Change-Id: I137d7952a540db18a616da4a944a2a44f27435da --- .../ui/AbstractLegacyComponentConnector.java | 39 ++ .../main/java/com/vaadin/v7/client/ui/VUpload.java | 394 +++++++++++++++++++++ .../v7/client/ui/upload/UploadConnector.java | 112 ++++++ .../ui/upload/UploadIFrameOnloadStrategy.java | 39 ++ .../ui/upload/UploadIFrameOnloadStrategyIE.java | 42 +++ 5 files changed, 626 insertions(+) create mode 100644 compatibility-client/src/main/java/com/vaadin/v7/client/ui/AbstractLegacyComponentConnector.java create mode 100644 compatibility-client/src/main/java/com/vaadin/v7/client/ui/VUpload.java create mode 100644 compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadConnector.java create mode 100644 compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadIFrameOnloadStrategy.java create mode 100644 compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadIFrameOnloadStrategyIE.java (limited to 'compatibility-client') diff --git a/compatibility-client/src/main/java/com/vaadin/v7/client/ui/AbstractLegacyComponentConnector.java b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/AbstractLegacyComponentConnector.java new file mode 100644 index 0000000000..a9fe473ad4 --- /dev/null +++ b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/AbstractLegacyComponentConnector.java @@ -0,0 +1,39 @@ +/* + * Copyright 2000-2016 Vaadin Ltd. + * + * 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.vaadin.v7.client.ui; + +import com.vaadin.client.ui.AbstractComponentConnector; +import com.vaadin.client.ui.AbstractConnector; +import com.vaadin.shared.communication.ServerRpc; + +/** + * Legacy connector for Vaadin 7 compatibility connectors. Needed because + * immediate has been removed from {@link AbstractConnector} in + * Vaadin 8. + * + * @author Vaadin Ltd + * @since 8.0 + */ +public class AbstractLegacyComponentConnector + extends AbstractComponentConnector { + + // overridden to be visible to VUpload in the same package. Without making + // it public in VUploadConnector + @Override + protected T getRpcProxy(Class rpcInterface) { + return super.getRpcProxy(rpcInterface); + } +} diff --git a/compatibility-client/src/main/java/com/vaadin/v7/client/ui/VUpload.java b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/VUpload.java new file mode 100644 index 0000000000..16108792b4 --- /dev/null +++ b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/VUpload.java @@ -0,0 +1,394 @@ +/* + * Copyright 2000-2016 Vaadin Ltd. + * + * 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.vaadin.v7.client.ui; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.FormElement; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.Event; +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.FormPanel; +import com.google.gwt.user.client.ui.Hidden; +import com.google.gwt.user.client.ui.Panel; +import com.google.gwt.user.client.ui.SimplePanel; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.ConnectorMap; +import com.vaadin.client.StyleConstants; +import com.vaadin.client.VConsole; +import com.vaadin.client.ui.VButton; +import com.vaadin.v7.client.ui.upload.UploadConnector; +import com.vaadin.v7.client.ui.upload.UploadIFrameOnloadStrategy; +import com.vaadin.v7.shared.ui.upload.UploadServerRpc; + +/** + * + * Note, we are not using GWT FormPanel as we want to listen submitcomplete + * events even though the upload component is already detached. + * + */ +public class VUpload extends SimplePanel { + + private final class MyFileUpload extends FileUpload { + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (event.getTypeInt() == Event.ONCHANGE) { + if (immediate && fu.getFilename() != null + && !"".equals(fu.getFilename())) { + submit(); + } + } else if (BrowserInfo.get().isIE() + && event.getTypeInt() == Event.ONFOCUS) { + // IE and user has clicked on hidden textarea part of upload + // field. Manually open file selector, other browsers do it by + // default. + fireNativeClick(fu.getElement()); + // also remove focus to enable hack if user presses cancel + // button + fireNativeBlur(fu.getElement()); + } + } + } + + public static final String CLASSNAME = "v-upload"; + + /** + * FileUpload component that opens native OS dialog to select file. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public FileUpload fu = new MyFileUpload(); + + Panel panel = new FlowPanel(); + + UploadIFrameOnloadStrategy onloadstrategy = GWT + .create(UploadIFrameOnloadStrategy.class); + + /** For internal use only. May be removed or replaced in the future. */ + public ApplicationConnection client; + + /** For internal use only. May be removed or replaced in the future. */ + public String paintableId; + + /** + * Button that initiates uploading. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public final VButton submitButton; + + /** + * When expecting big files, programmer may initiate some UI changes when + * uploading the file starts. Bit after submitting file we'll visit the + * server to check possible changes. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public Timer t; + + /** + * some browsers tries to send form twice if submit is called in button + * click handler, some don't submit at all without it, so we need to track + * if form is already being submitted + */ + private boolean submitted = false; + + private boolean enabled = true; + + private boolean immediate; + + private Hidden maxfilesize = new Hidden(); + + /** For internal use only. May be removed or replaced in the future. */ + public FormElement element; + + private com.google.gwt.dom.client.Element synthesizedFrame; + + /** For internal use only. May be removed or replaced in the future. */ + public int nextUploadId; + + public VUpload() { + super(com.google.gwt.dom.client.Document.get().createFormElement()); + + element = getElement().cast(); + setEncoding(getElement(), FormPanel.ENCODING_MULTIPART); + element.setMethod(FormPanel.METHOD_POST); + + setWidget(panel); + panel.add(maxfilesize); + panel.add(fu); + submitButton = new VButton(); + submitButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + if (immediate) { + // fire click on upload (eg. focused button and hit space) + fireNativeClick(fu.getElement()); + } else { + submit(); + } + } + }); + panel.add(submitButton); + + setStyleName(CLASSNAME); + } + + private static native void setEncoding(Element form, String encoding) + /*-{ + form.enctype = encoding; + }-*/; + + /** For internal use only. May be removed or replaced in the future. */ + public void setImmediate(boolean booleanAttribute) { + if (immediate != booleanAttribute) { + immediate = booleanAttribute; + if (immediate) { + fu.sinkEvents(Event.ONCHANGE); + fu.sinkEvents(Event.ONFOCUS); + } + } + setStyleName(getElement(), CLASSNAME + "-immediate", immediate); + } + + private static native void fireNativeClick(Element element) + /*-{ + element.click(); + }-*/; + + private static native void fireNativeBlur(Element element) + /*-{ + element.blur(); + }-*/; + + /** For internal use only. May be removed or replaced in the future. */ + public void disableUpload() { + setEnabledForSubmitButton(false); + if (!submitted) { + // Cannot disable the fileupload while submitting or the file won't + // be submitted at all + fu.getElement().setPropertyBoolean("disabled", true); + } + enabled = false; + } + + /** For internal use only. May be removed or replaced in the future. */ + public void enableUpload() { + setEnabledForSubmitButton(true); + fu.getElement().setPropertyBoolean("disabled", false); + enabled = true; + if (submitted) { + /* + * An old request is still in progress (most likely cancelled), + * ditching that target frame to make it possible to send a new + * file. A new target frame is created later." + */ + cleanTargetFrame(); + submitted = false; + } + } + + private void setEnabledForSubmitButton(boolean enabled) { + submitButton.setEnabled(enabled); + submitButton.setStyleName(StyleConstants.DISABLED, !enabled); + } + + /** + * Re-creates file input field and populates panel. This is needed as we + * want to clear existing values from our current file input field. + */ + private void rebuildPanel() { + panel.remove(submitButton); + panel.remove(fu); + fu = new MyFileUpload(); + fu.setName(paintableId + "_file"); + fu.getElement().setPropertyBoolean("disabled", !enabled); + panel.add(fu); + panel.add(submitButton); + if (immediate) { + fu.sinkEvents(Event.ONCHANGE); + } + } + + /** + * Called by JSNI (hooked via {@link #onloadstrategy}) + */ + private void onSubmitComplete() { + /* Needs to be run dereferred to avoid various browser issues. */ + Scheduler.get().scheduleDeferred(new Command() { + @Override + public void execute() { + if (submitted) { + if (client != null) { + if (t != null) { + t.cancel(); + } + VConsole.log("VUpload:Submit complete"); + ((UploadConnector) ConnectorMap.get(client) + .getConnector(VUpload.this)) + .getRpcProxy(UploadServerRpc.class) + .poll(); + } + + rebuildPanel(); + + submitted = false; + enableUpload(); + if (!isAttached()) { + /* + * Upload is complete when upload is already abandoned. + */ + cleanTargetFrame(); + } + } + } + }); + } + + ScheduledCommand startUploadCmd = new ScheduledCommand() { + + @Override + public void execute() { + element.submit(); + submitted = true; + + disableUpload(); + + /* + * Visit server a moment after upload has started to see possible + * changes from UploadStarted event. Will be cleared on complete. + * + * Must get the id here as the upload can finish before the timer + * expires and in that case nextUploadId has been updated and is + * wrong. + */ + final int thisUploadId = nextUploadId; + t = new Timer() { + @Override + public void run() { + // Only visit the server if the upload has not already + // finished + if (thisUploadId == nextUploadId) { + VConsole.log( + "Visiting server to see if upload started event changed UI."); + client.updateVariable(paintableId, "pollForStart", + thisUploadId, true); + } + } + }; + t.schedule(800); + } + + }; + + /** For internal use only. May be removed or replaced in the future. */ + public void submit() { + if (submitted || !enabled) { + VConsole.log("Submit cancelled (disabled or already submitted)"); + return; + } + if (fu.getFilename().length() == 0) { + VConsole.log("Submitting empty selection (no file)"); + } + // flush possibly pending variable changes, so they will be handled + // before upload + client.sendPendingVariableChanges(); + + // This is done as deferred because sendPendingVariableChanges is also + // deferred and we want to start the upload only after the changes have + // been sent to the server + Scheduler.get().scheduleDeferred(startUploadCmd); + } + + /** For internal use only. May be removed or replaced in the future. */ + public void disableTitle(boolean disable) { + if (disable) { + // Disable title attribute for upload element. + if (BrowserInfo.get().isChrome()) { + // In Chrome title has to be set to " " to make it invisible + fu.setTitle(" "); + } else if (BrowserInfo.get().isFirefox()) { + // In FF title has to be set to empty string to make it + // invisible + // Method setTitle removes title attribute when it's an empty + // string, so setAttribute() should be used here + fu.getElement().setAttribute("title", ""); + } + // For other browsers absent title doesn't show default tooltip for + // input element + } else { + fu.setTitle(null); + } + } + + @Override + protected void onAttach() { + super.onAttach(); + if (client != null) { + ensureTargetFrame(); + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public void ensureTargetFrame() { + if (synthesizedFrame == null) { + // Attach a hidden IFrame to the form. This is the target iframe to + // which the form will be submitted. We have to create the iframe + // using innerHTML, because setting an iframe's 'name' property + // dynamically doesn't work on most browsers. + DivElement dummy = Document.get().createDivElement(); + dummy.setInnerHTML("