You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

VUpload.java 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client.ui;
  5. import com.google.gwt.core.client.GWT;
  6. import com.google.gwt.dom.client.DivElement;
  7. import com.google.gwt.dom.client.Document;
  8. import com.google.gwt.dom.client.FormElement;
  9. import com.google.gwt.event.dom.client.ClickEvent;
  10. import com.google.gwt.event.dom.client.ClickHandler;
  11. import com.google.gwt.user.client.Command;
  12. import com.google.gwt.user.client.DeferredCommand;
  13. import com.google.gwt.user.client.Element;
  14. import com.google.gwt.user.client.Event;
  15. import com.google.gwt.user.client.Timer;
  16. import com.google.gwt.user.client.ui.FileUpload;
  17. import com.google.gwt.user.client.ui.FlowPanel;
  18. import com.google.gwt.user.client.ui.FormPanel;
  19. import com.google.gwt.user.client.ui.Hidden;
  20. import com.google.gwt.user.client.ui.Panel;
  21. import com.google.gwt.user.client.ui.SimplePanel;
  22. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  23. import com.vaadin.terminal.gwt.client.Paintable;
  24. import com.vaadin.terminal.gwt.client.UIDL;
  25. /**
  26. *
  27. * Note, we are not using GWT FormPanel as we want to listen submitcomplete
  28. * events even though the upload component is already detached.
  29. *
  30. */
  31. public class VUpload extends SimplePanel implements Paintable {
  32. private final class MyFileUpload extends FileUpload {
  33. @Override
  34. public void onBrowserEvent(Event event) {
  35. super.onBrowserEvent(event);
  36. if (event.getTypeInt() == Event.ONCHANGE) {
  37. if (immediate && fu.getFilename() != null
  38. && !"".equals(fu.getFilename())) {
  39. submit();
  40. }
  41. } else if (event.getTypeInt() == Event.ONFOCUS) {
  42. // IE and user has clicked on hidden textarea part of upload
  43. // field. Manually open file selector, other browsers do it by
  44. // default.
  45. fireNativeClick(fu.getElement());
  46. // also remove focus to enable hack if user presses cancel
  47. // button
  48. fireNativeBlur(fu.getElement());
  49. }
  50. }
  51. }
  52. public static final String CLASSNAME = "v-upload";
  53. /**
  54. * FileUpload component that opens native OS dialog to select file.
  55. */
  56. FileUpload fu = new MyFileUpload();
  57. Panel panel = new FlowPanel();
  58. UploadIFrameOnloadStrategy onloadstrategy = GWT
  59. .create(UploadIFrameOnloadStrategy.class);
  60. ApplicationConnection client;
  61. private String paintableId;
  62. /**
  63. * Button that initiates uploading
  64. */
  65. private final VButton submitButton;
  66. /**
  67. * When expecting big files, programmer may initiate some UI changes when
  68. * uploading the file starts. Bit after submitting file we'll visit the
  69. * server to check possible changes.
  70. */
  71. private Timer t;
  72. /**
  73. * some browsers tries to send form twice if submit is called in button
  74. * click handler, some don't submit at all without it, so we need to track
  75. * if form is already being submitted
  76. */
  77. private boolean submitted = false;
  78. private boolean enabled = true;
  79. private boolean immediate;
  80. private Hidden maxfilesize = new Hidden();
  81. private FormElement element;
  82. private com.google.gwt.dom.client.Element synthesizedFrame;
  83. private int nextUploadId;
  84. public VUpload() {
  85. super(com.google.gwt.dom.client.Document.get().createFormElement());
  86. element = getElement().cast();
  87. setEncoding(getElement(), FormPanel.ENCODING_MULTIPART);
  88. element.setMethod(FormPanel.METHOD_POST);
  89. setWidget(panel);
  90. panel.add(maxfilesize);
  91. panel.add(fu);
  92. submitButton = new VButton();
  93. submitButton.addClickHandler(new ClickHandler() {
  94. public void onClick(ClickEvent event) {
  95. if (immediate) {
  96. // fire click on upload (eg. focused button and hit space)
  97. fireNativeClick(fu.getElement());
  98. } else {
  99. submit();
  100. }
  101. }
  102. });
  103. panel.add(submitButton);
  104. setStyleName(CLASSNAME);
  105. }
  106. private static native void setEncoding(Element form, String encoding)
  107. /*-{
  108. form.enctype = encoding;
  109. // For IE6
  110. form.encoding = encoding;
  111. }-*/;
  112. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  113. if (client.updateComponent(this, uidl, true)) {
  114. return;
  115. }
  116. if (uidl.hasAttribute("notStarted")) {
  117. t.schedule(400);
  118. return;
  119. }
  120. setImmediate(uidl.getBooleanAttribute("immediate"));
  121. this.client = client;
  122. paintableId = uidl.getId();
  123. nextUploadId = uidl.getIntAttribute("nextid");
  124. element.setAction(client.getAppUri());
  125. submitButton.setText(uidl.getStringAttribute("buttoncaption"));
  126. fu.setName(paintableId + "_file");
  127. if (uidl.hasAttribute("disabled") || uidl.hasAttribute("readonly")) {
  128. disableUpload();
  129. } else if (!uidl.getBooleanAttribute("state")) {
  130. // Enable the button only if an upload is not in progress
  131. enableUpload();
  132. ensureTargetFrame();
  133. }
  134. }
  135. private void setImmediate(boolean booleanAttribute) {
  136. if (immediate != booleanAttribute) {
  137. immediate = booleanAttribute;
  138. if (immediate) {
  139. fu.sinkEvents(Event.ONCHANGE);
  140. fu.sinkEvents(Event.ONFOCUS);
  141. }
  142. }
  143. setStyleName(getElement(), CLASSNAME + "-immediate", immediate);
  144. }
  145. private static native void fireNativeClick(Element element)
  146. /*-{
  147. element.click();
  148. }-*/;
  149. private static native void fireNativeBlur(Element element)
  150. /*-{
  151. element.blur();
  152. }-*/;
  153. protected void disableUpload() {
  154. submitButton.setEnabled(false);
  155. if (!submitted) {
  156. // Cannot disable the fileupload while submitting or the file won't
  157. // be submitted at all
  158. fu.getElement().setPropertyBoolean("disabled", true);
  159. }
  160. enabled = false;
  161. }
  162. protected void enableUpload() {
  163. submitButton.setEnabled(true);
  164. fu.getElement().setPropertyBoolean("disabled", false);
  165. enabled = true;
  166. }
  167. /**
  168. * Re-creates file input field and populates panel. This is needed as we
  169. * want to clear existing values from our current file input field.
  170. */
  171. private void rebuildPanel() {
  172. panel.remove(submitButton);
  173. panel.remove(fu);
  174. fu = new MyFileUpload();
  175. fu.setName(paintableId + "_file");
  176. fu.getElement().setPropertyBoolean("disabled", !enabled);
  177. panel.add(fu);
  178. panel.add(submitButton);
  179. if (immediate) {
  180. fu.sinkEvents(Event.ONCHANGE);
  181. }
  182. }
  183. /**
  184. * Called by JSNI (hooked via {@link #onloadstrategy})
  185. */
  186. @SuppressWarnings("unused")
  187. private void onSubmitComplete() {
  188. /* Needs to be run dereferred to avoid various browser issues. */
  189. DeferredCommand.addCommand(new Command() {
  190. public void execute() {
  191. if (client != null) {
  192. if (t != null) {
  193. t.cancel();
  194. }
  195. ApplicationConnection.getConsole().log("Submit complete");
  196. client.sendPendingVariableChanges();
  197. }
  198. rebuildPanel();
  199. submitted = false;
  200. enableUpload();
  201. if (!isAttached()) {
  202. /*
  203. * Upload is complete when upload is already abandoned.
  204. */
  205. cleanTargetFrame();
  206. }
  207. }
  208. });
  209. }
  210. private void submit() {
  211. if (fu.getFilename().length() == 0 || submitted || !enabled) {
  212. ApplicationConnection
  213. .getConsole()
  214. .log("Submit cancelled (disabled, no file or already submitted)");
  215. return;
  216. }
  217. // flush possibly pending variable changes, so they will be handled
  218. // before upload
  219. client.sendPendingVariableChanges();
  220. element.submit();
  221. submitted = true;
  222. ApplicationConnection.getConsole().log("Submitted form");
  223. disableUpload();
  224. /*
  225. * Visit server a moment after upload has started to see possible
  226. * changes from UploadStarted event. Will be cleared on complete.
  227. */
  228. t = new Timer() {
  229. @Override
  230. public void run() {
  231. ApplicationConnection
  232. .getConsole()
  233. .log("Visiting server to see if upload started event changed UI.");
  234. client.updateVariable(paintableId, "pollForStart",
  235. nextUploadId, true);
  236. }
  237. };
  238. t.schedule(800);
  239. }
  240. @Override
  241. protected void onAttach() {
  242. super.onAttach();
  243. if (client != null) {
  244. ensureTargetFrame();
  245. }
  246. }
  247. private void ensureTargetFrame() {
  248. if (synthesizedFrame == null) {
  249. // Attach a hidden IFrame to the form. This is the target iframe to
  250. // which
  251. // the form will be submitted. We have to create the iframe using
  252. // innerHTML,
  253. // because setting an iframe's 'name' property dynamically doesn't
  254. // work on
  255. // most browsers.
  256. DivElement dummy = Document.get().createDivElement();
  257. dummy.setInnerHTML("<iframe src=\"javascript:''\" name='"
  258. + getFrameName()
  259. + "' style='position:absolute;width:0;height:0;border:0'>");
  260. synthesizedFrame = dummy.getFirstChildElement();
  261. Document.get().getBody().appendChild(synthesizedFrame);
  262. element.setTarget(getFrameName());
  263. onloadstrategy.hookEvents(synthesizedFrame, this);
  264. }
  265. }
  266. private String getFrameName() {
  267. return paintableId + "_TGT_FRAME";
  268. }
  269. @Override
  270. protected void onDetach() {
  271. super.onDetach();
  272. if (!submitted) {
  273. cleanTargetFrame();
  274. }
  275. }
  276. private void cleanTargetFrame() {
  277. if (synthesizedFrame != null) {
  278. Document.get().getBody().removeChild(synthesizedFrame);
  279. onloadstrategy.unHookEvents(synthesizedFrame);
  280. synthesizedFrame = null;
  281. }
  282. }
  283. }