diff options
author | Pekka Hyvönen <pekka@vaadin.com> | 2016-10-06 16:19:53 +0300 |
---|---|---|
committer | Vaadin Code Review <review@vaadin.com> | 2016-10-07 11:53:56 +0000 |
commit | 8cdaeb241a7e57561e69c2ee46d0dde54e591c45 (patch) | |
tree | 79a3ba8d1b16a686ce52eca3955df4b5a56b2e21 /compatibility-client | |
parent | d383722053216a97bf75f9623a060a7edd48a01f (diff) | |
download | vaadin-framework-8cdaeb241a7e57561e69c2ee46d0dde54e591c45.tar.gz vaadin-framework-8cdaeb241a7e57561e69c2ee46d0dde54e591c45.zip |
Add Upload to compatibility packages
Step 1 / 2 of adding a new Upload Component.
Change-Id: I137d7952a540db18a616da4a944a2a44f27435da
Diffstat (limited to 'compatibility-client')
5 files changed, 626 insertions, 0 deletions
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 + * <code>immediate</code> 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 extends ServerRpc> T getRpcProxy(Class<T> 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. + * <p> + * 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. + * <p> + * 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. + * <p> + * 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("<iframe src=\"javascript:''\" name='" + + getFrameName() + + "' style='position:absolute;width:0;height:0;border:0'>"); + synthesizedFrame = dummy.getFirstChildElement(); + Document.get().getBody().appendChild(synthesizedFrame); + element.setTarget(getFrameName()); + onloadstrategy.hookEvents(synthesizedFrame, this); + } + } + + private String getFrameName() { + return paintableId + "_TGT_FRAME"; + } + + @Override + protected void onDetach() { + super.onDetach(); + if (!submitted) { + cleanTargetFrame(); + } + } + + private void cleanTargetFrame() { + if (synthesizedFrame != null) { + Document.get().getBody().removeChild(synthesizedFrame); + onloadstrategy.unHookEvents(synthesizedFrame); + synthesizedFrame = null; + } + } + +} diff --git a/compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadConnector.java b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadConnector.java new file mode 100644 index 0000000000..eefbe60a1a --- /dev/null +++ b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadConnector.java @@ -0,0 +1,112 @@ +/* + * 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.upload; + +import com.google.gwt.event.dom.client.ChangeEvent; +import com.google.gwt.event.dom.client.ChangeHandler; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.Paintable; +import com.vaadin.client.UIDL; +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.shared.EventId; +import com.vaadin.shared.ui.Connect; +import com.vaadin.v7.client.ui.AbstractLegacyComponentConnector; +import com.vaadin.v7.client.ui.VUpload; +import com.vaadin.v7.shared.ui.upload.UploadClientRpc; +import com.vaadin.v7.shared.ui.upload.UploadServerRpc; +import com.vaadin.v7.shared.ui.upload.UploadState; + +@Connect(com.vaadin.v7.ui.Upload.class) +public class UploadConnector extends AbstractLegacyComponentConnector + implements Paintable { + + public UploadConnector() { + registerRpc(UploadClientRpc.class, new UploadClientRpc() { + @Override + public void submitUpload() { + getWidget().submit(); + } + }); + } + + @Override + protected void init() { + super.init(); + + getWidget().fu.addChangeHandler(new ChangeHandler() { + @Override + public void onChange(ChangeEvent event) { + if (hasEventListener(EventId.CHANGE)) { + getRpcProxy(UploadServerRpc.class) + .change(getWidget().fu.getFilename()); + } + } + }); + } + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (!isRealUpdate(uidl)) { + return; + } + if (uidl.hasAttribute("notStarted")) { + getWidget().t.schedule(400); + return; + } + getWidget().setImmediate(getState().immediate); + getWidget().client = client; + getWidget().paintableId = uidl.getId(); + getWidget().nextUploadId = uidl.getIntAttribute("nextid"); + final String action = client + .translateVaadinUri(uidl.getStringVariable("action")); + getWidget().element.setAction(action); + if (uidl.hasAttribute("buttoncaption")) { + getWidget().submitButton + .setText(uidl.getStringAttribute("buttoncaption")); + getWidget().submitButton.setVisible(true); + } else { + getWidget().submitButton.setVisible(false); + } + getWidget().fu.setName(getWidget().paintableId + "_file"); + + if (!isEnabled() || isReadOnly()) { + getWidget().disableUpload(); + } else if (!uidl.getBooleanAttribute("state")) { + // Enable the button only if an upload is not in progress + getWidget().enableUpload(); + getWidget().ensureTargetFrame(); + } + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + getWidget().disableTitle(hasTooltip()); + } + + @Override + public VUpload getWidget() { + return (VUpload) super.getWidget(); + } + + @Override + public UploadState getState() { + return (UploadState) super.getState(); + } + +} diff --git a/compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadIFrameOnloadStrategy.java b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadIFrameOnloadStrategy.java new file mode 100644 index 0000000000..c01fce1b50 --- /dev/null +++ b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadIFrameOnloadStrategy.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.upload; + +import com.vaadin.v7.client.ui.VUpload; + +public class UploadIFrameOnloadStrategy { + + public native void hookEvents(com.google.gwt.dom.client.Element iframe, + VUpload upload) + /*-{ + iframe.onload = $entry(function() { + upload.@com.vaadin.client.ui.VUpload::onSubmitComplete()(); + }); + }-*/; + + /** + * @param iframe + * the iframe whose onLoad event is to be cleaned + */ + public native void unHookEvents(com.google.gwt.dom.client.Element iframe) + /*-{ + iframe.onload = null; + }-*/; + +} diff --git a/compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadIFrameOnloadStrategyIE.java b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadIFrameOnloadStrategyIE.java new file mode 100644 index 0000000000..a40abfafdf --- /dev/null +++ b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadIFrameOnloadStrategyIE.java @@ -0,0 +1,42 @@ +/* + * 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.upload; + +import com.google.gwt.dom.client.Element; +import com.vaadin.v7.client.ui.VUpload; + +/** + * IE does not have onload, detect onload via readystatechange + * + */ +public class UploadIFrameOnloadStrategyIE extends UploadIFrameOnloadStrategy { + @Override + public native void hookEvents(Element iframe, VUpload upload) + /*-{ + iframe.onreadystatechange = $entry(function() { + if (iframe.readyState == 'complete') { + upload.@com.vaadin.client.ui.VUpload::onSubmitComplete()(); + } + }); + }-*/; + + @Override + public native void unHookEvents(Element iframe) + /*-{ + iframe.onreadystatechange = null; + }-*/; + +} |