Java tutorial
/* * Copyright 2013 cruxframework.org. * * 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 org.cruxframework.crux.widgets.client.uploader; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.cruxframework.crux.core.client.file.Blob; import org.cruxframework.crux.core.client.file.File; import org.cruxframework.crux.core.client.file.FileList; import org.cruxframework.crux.core.client.file.FileReader; import org.cruxframework.crux.core.client.file.FileReader.ReaderStringCallback; import org.cruxframework.crux.core.client.xhr.XMLHttpRequest2; import org.cruxframework.crux.widgets.client.button.Button; import org.cruxframework.crux.widgets.client.event.SelectEvent; import org.cruxframework.crux.widgets.client.event.SelectHandler; import org.cruxframework.crux.widgets.client.progressbar.ProgressBar; import org.cruxframework.crux.widgets.client.uploader.event.AddFileEvent; import org.cruxframework.crux.widgets.client.uploader.event.AddFileHandler; import org.cruxframework.crux.widgets.client.uploader.event.HasAddFileHandlers; import org.cruxframework.crux.widgets.client.uploader.event.HasRemoveFileHandlers; import org.cruxframework.crux.widgets.client.uploader.event.HasUploadCanceledHandlers; import org.cruxframework.crux.widgets.client.uploader.event.HasUploadCompleteHandlers; import org.cruxframework.crux.widgets.client.uploader.event.HasUploadErrorHandlers; import org.cruxframework.crux.widgets.client.uploader.event.HasUploadStartHandlers; import org.cruxframework.crux.widgets.client.uploader.event.RemoveFileEvent; import org.cruxframework.crux.widgets.client.uploader.event.RemoveFileEvent.FileRemoveAction; import org.cruxframework.crux.widgets.client.uploader.event.RemoveFileHandler; import org.cruxframework.crux.widgets.client.uploader.event.UploadCanceledEvent; import org.cruxframework.crux.widgets.client.uploader.event.UploadCanceledHandler; import org.cruxframework.crux.widgets.client.uploader.event.UploadCompleteEvent; import org.cruxframework.crux.widgets.client.uploader.event.UploadCompleteHandler; import org.cruxframework.crux.widgets.client.uploader.event.UploadErrorEvent; import org.cruxframework.crux.widgets.client.uploader.event.UploadErrorHandler; import org.cruxframework.crux.widgets.client.uploader.event.UploadStartEvent; import org.cruxframework.crux.widgets.client.uploader.event.UploadStartHandler; import com.google.gwt.core.client.JsArray; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.PartialSupport; import com.google.gwt.dom.client.Style.Display; import com.google.gwt.dom.client.Style.Float; import com.google.gwt.dom.client.Style.Visibility; import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HasEnabled; import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.xhr.client.ReadyStateChangeHandler; import com.google.gwt.xhr.client.XMLHttpRequest; /** * An HTML5 based file uploader widget. * * @author Thiago da Rosa de Bustamante */ @PartialSupport abstract class AbstractFileUploader extends Composite implements HasEnabled, HasAddFileHandlers, HasRemoveFileHandlers, HasUploadStartHandlers, HasUploadErrorHandlers, HasUploadCompleteHandlers, HasUploadCanceledHandlers { public static final String SUPPORTED_IMAGES_MIMETYPES = "image/jpg," + "image/jpeg," + "image/tif," + "image/gif," + "image/png," + "image/raw" + "image/svg+xml"; public static final String SUPPORTED_IMAGES_EXTENSIONS = "jpg," + "jpeg," + "tif," + "gif," + "png," + "raw" + "svg"; static final int HTTP_STATUS_NON_HTTP = 0; static final int HTTP_STATUS_OK = 200; static final String HTTP_POST = "POST"; protected FlowPanel mainPanel; protected FlowPanel filesPanel; protected FileButton fileInput; protected Map<String, Blob> files = new HashMap<String, Blob>(); protected Map<String, FlowPanel> filePanelWidgets = new HashMap<String, FlowPanel>(); protected String url; protected boolean autoUploadFiles = false; protected boolean showProgressBar = true; protected Button sendButton; //TODO: Implement this behavior protected boolean enabled = true; //Indicates if user has informed at least one remove file handler. //If so, file will only be removed is the removeAction is declared inside the event. private boolean hasRemoveFileHandler; /** * Protected Constructor. Use createIfSupported() to instantiate. */ protected AbstractFileUploader() { initializeWidgets(); initWidget(mainPanel); setStyleName("crux-FileUploader"); } protected abstract void initializeWidgets(); public String getUrl() { return url; } @Override public HandlerRegistration addAddFileHandler(AddFileHandler handler) { return addHandler(handler, AddFileEvent.getType()); } @Override public HandlerRegistration addRemoveFileHandler(RemoveFileHandler handler) { this.hasRemoveFileHandler = true; return addHandler(handler, RemoveFileEvent.getType()); } @Override public HandlerRegistration addUploadStartHandler(UploadStartHandler handler) { return addHandler(handler, UploadStartEvent.getType()); } @Override public HandlerRegistration addUploadCompleteHandler(UploadCompleteHandler handler) { return addHandler(handler, UploadCompleteEvent.getType()); } @Override public HandlerRegistration addUploadCanceledHandler(UploadCanceledHandler handler) { return addHandler(handler, UploadCanceledEvent.getType()); } @Override public HandlerRegistration addUploadErrorHandler(UploadErrorHandler handler) { return addHandler(handler, UploadErrorEvent.getType()); } public void setFileInputText(String text) { fileInput.setText(text); } public void setSendButtonText(String text) { sendButton.setText(text); } public void setUrl(String url) { this.url = url; } public boolean isAutoUploadFiles() { return autoUploadFiles; } public void setAutoUploadFiles(boolean autoUploadFiles) { this.autoUploadFiles = autoUploadFiles; sendButton.setVisible(!autoUploadFiles); } public boolean isMultiple() { return fileInput.isMultiple(); } public void setMultiple(boolean multiple) { fileInput.setMultiple(multiple); } public Iterator<Blob> iterateFiles() { return files.values().iterator(); } public void uploadFile(String fileName) { uploadFile(fileName, url); } public void uploadFile(String fileName, String url) { uploadFile(files.get(fileName), fileName, url); } public void uploadFile(Blob file, String fileName) { uploadFile(file, fileName, url); } public void uploadFile(final Blob file, String fileName, String url) { UploadStartEvent uploadStartEvent = UploadStartEvent.fire(this, file, fileName); if (uploadStartEvent.isCanceled()) { UploadCanceledEvent.fire(this, file, fileName); } else { XMLHttpRequest2 xhr = getXhr(fileName); xhr.open(HTTP_POST, url); xhr.send("file", file); } } public void uploadAllFiles() { uploadAllFiles(url); } public void uploadAllFiles(String url) { for (String fileName : files.keySet()) { uploadFile(fileName, url); } } public void removeFile(final String fileName) { final Widget filePanel = filePanelWidgets.get(fileName); if (filePanel != null) { if (!hasRemoveFileHandler) { filePanelWidgets.remove(fileName); filePanel.removeFromParent(); files.remove(fileName); } else { Blob removedFile = files.get(fileName); RemoveFileEvent.fire(this, removedFile, fileName, new FileRemoveAction() { @Override public void removeFile() { filePanelWidgets.remove(fileName); filePanel.removeFromParent(); files.remove(fileName); } }); } } } public void clear() { Iterator<String> keys = files.keySet().iterator(); while (keys.hasNext()) { String key = keys.next(); keys.remove(); Widget filePanel = filePanelWidgets.get(key); if (filePanel != null) { filePanelWidgets.remove(key); filePanel.removeFromParent(); } } } public void addFile(Blob file, String fileName) { processFile(file, fileName); } public boolean isShowProgressBar() { return showProgressBar; } public void setShowProgressBar(boolean showProgressBar) { this.showProgressBar = showProgressBar; } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public Blob getFile(String fileName) { return files.get(fileName); } protected FlowPanel initFilesPanel() { FlowPanel filesPanel = new FlowPanel(); filesPanel.setStyleName("filesArea"); return filesPanel; } protected FlowPanel initButtonsPanel() { FlowPanel buttonsPanel = new FlowPanel(); buttonsPanel.setStyleName("buttonsPanel"); fileInput = initFileInput(); buttonsPanel.add(fileInput); sendButton = initSendButton(); buttonsPanel.add(sendButton); return buttonsPanel; } protected Button initSendButton() { Button sendButton = new Button(); sendButton.setStyleName("sendButton"); sendButton.getElement().getStyle().setDisplay(Display.INLINE_BLOCK); sendButton.addSelectHandler(new SelectHandler() { public void onSelect(SelectEvent event) { uploadAllFiles(); } }); return sendButton; } protected FileButton initFileInput() { FileButton fileInput = new FileButton(); fileInput.setStyleName("fileInputButton"); fileInput.getElement().getStyle().setDisplay(Display.INLINE_BLOCK); fileInput.addChangeHandler(new ChangeHandler() { public void onChange(ChangeEvent event) { onSelectFile(event); } }); return fileInput; } protected void onSelectFile(ChangeEvent event) { event.stopPropagation(); event.preventDefault(); processFiles(event.getNativeEvent()); resetFileInput(); } protected void resetFileInput() { FlowPanel buttonsPanel = (FlowPanel) fileInput.getParent(); int index = buttonsPanel.getWidgetIndex(fileInput); buttonsPanel.remove(index); String text = fileInput.getText(); boolean multiple = fileInput.isMultiple(); fileInput = initFileInput(); fileInput.setText(text); fileInput.setMultiple(multiple); buttonsPanel.insert(fileInput, index); } protected void processFiles(NativeEvent event) { JsArray<File> files = getFiles(event); if (files.length() > 0 && !isMultiple()) { clear(); } for (int i = 0; i < files.length(); i++) { File file = files.get(i); AddFileEvent addFileEvent = AddFileEvent.fire(this, file, file.getName()); if (!addFileEvent.isCanceled()) { boolean processed = processFile(file, file.getName()); if (processed && isAutoUploadFiles()) { uploadFile(file, file.getName()); } } } } protected boolean processFile(Blob file, String fileName) { if (!filePanelWidgets.containsKey(fileName)) { if (isMultiple() || files.size() < 1) { files.put(fileName, file); FlowPanel filePanel = createFilePanel(fileName, file); filePanelWidgets.put(fileName, filePanel); filesPanel.add(filePanel); return true; } } return false; } protected FlowPanel createFilePanel(String fileName, Blob file) { FlowPanel filePanel = new FlowPanel(); filePanel.setStyleName("filePanel"); filePanel.getElement().getStyle().setDisplay(Display.INLINE_BLOCK); filePanel.add(createDeleteButton(fileName)); createThumbnailIfSupported(file, filePanel); filePanel.add(createNameLabel(fileName)); if (showProgressBar) { filePanel.add(createProgressBar()); } return filePanel; } private String retrieveFileExtension(String fileName) { String extension = ""; int i = fileName.lastIndexOf('.'); if (i > 0) { extension = fileName.substring(i + 1); } return extension; } protected void createThumbnailIfSupported(Blob file, final FlowPanel filePanel) { FileReader fileReader = FileReader.createIfSupported(); // if is supported and is image create it, otherwise create a visual fallback. // image must have mimetype AND extension of images to pass if (fileReader == null || !SUPPORTED_IMAGES_MIMETYPES.contains(file.getType()) || !SUPPORTED_IMAGES_EXTENSIONS.contains(this.retrieveFileExtension(((File) file).getName()))) { String fileExt = "." + this.retrieveFileExtension(((File) file).getName()); Label label = new Label(); label.setStyleName("thumbnailImage noPreview"); label.setText(fileExt); filePanel.add(label); return; } else { fileReader.readAsDataURL(file, new ReaderStringCallback() { public void onComplete(String result) { Image image = new Image(result); image.setStyleName("thumbnailImage"); image.getElement().getStyle().setFloat(Float.LEFT); filePanel.add(image); } }); } } protected Button createDeleteButton(final String fileName) { Button delete = new Button(); delete.setStyleName("cancelUploadButton"); delete.getElement().getStyle().setFloat(Float.LEFT); delete.addSelectHandler(new SelectHandler() { public void onSelect(SelectEvent event) { removeFile(fileName); } }); return delete; } protected Label createNameLabel(String fileName) { Label label = new Label(fileName); label.getElement().getStyle().setFloat(Float.LEFT); return label; } protected ProgressBar createProgressBar() { ProgressBar progressBar = new ProgressBar(); return progressBar; } protected ProgressBar getProgressBar(String fileName) { FlowPanel filePanel = filePanelWidgets.get(fileName); ProgressBar progressBar = (ProgressBar) filePanel.getWidget(filePanel.getWidgetCount() - 1); return progressBar; } protected Button getRemoveButton(String fileName) { FlowPanel filePanel = filePanelWidgets.get(fileName); Button button = (Button) filePanel.getWidget(0); return button; } protected void updateProgressBar(String fileName, double loaded, double total) { ProgressBar progressBar = getProgressBar(fileName); int percentLoaded = (int) Math.round((loaded / total) * 100); progressBar.update(percentLoaded); } protected XMLHttpRequest2 getXhr(final String fileName) { XMLHttpRequest2 xhr = XMLHttpRequest2.create(); xhr.setOnProgressHandler(new XMLHttpRequest2.ProgressHandler() { public void onProgress(double loaded, double total) { updateProgressBar(fileName, loaded, total); } }); xhr.setOnReadyStateChange(new ReadyStateChangeHandler() { public void onReadyStateChange(XMLHttpRequest xhr) { if (xhr.getReadyState() == XMLHttpRequest.DONE) { xhr.clearOnReadyStateChange(); if (getBrowserSpecificFailure(xhr) != null) { uploadError(fileName); } else { int status = xhr.getStatus() - 200; if (status >= 0 && status < 10) { concludeUpload(fileName); } else { uploadError(fileName); } } } } }); return xhr; } protected void uploadError(final String fileName) { getProgressBar(fileName).setError(true); UploadErrorEvent.fire(this, files.get(fileName), fileName); } protected void concludeUpload(String fileName) { getProgressBar(fileName).conclude(); getRemoveButton(fileName).getElement().getStyle().setVisibility(Visibility.HIDDEN); UploadCompleteEvent.fire(this, files.get(fileName), fileName); } protected native JsArray<File> getFiles(NativeEvent event)/*-{ return event.target.files || event.dataTransfer.files; }-*/; /** * Tests if the JavaScript <code>XmlHttpRequest.status</code> property is * readable. This can return failure in two different known scenarios: * * <ol> * <li>On Mozilla, after a network error, attempting to read the status code * results in an exception being thrown. See <a * href="https://bugzilla.mozilla.org/show_bug.cgi?id=238559" * >https://bugzilla.mozilla.org/show_bug.cgi?id=238559</a>.</li> * <li>On Safari, if the HTTP response does not include any response text. See * <a * href="http://bugs.webkit.org/show_bug.cgi?id=3810">http://bugs.webkit.org * /show_bug.cgi?id=3810</a>.</li> * </ol> * * @param xhr the JavaScript <code>XmlHttpRequest</code> object to test * @return a String message containing an error message if the * <code>XmlHttpRequest.status</code> code is unreadable or null if * the status code could be successfully read. */ private native String getBrowserSpecificFailure(XMLHttpRequest xhr) /*-{ try { if (xhr.status === undefined) { return "XmlHttpRequest.status == undefined, please see Safari bug " + "http://bugs.webkit.org/show_bug.cgi?id=3810 for more details"; } return null; } catch (e) { return "Unable to read XmlHttpRequest.status; likely causes are a " + "networking error or bad cross-domain request. Please see " + "https://bugzilla.mozilla.org/show_bug.cgi?id=238559 for more " + "details"; } }-*/; static boolean isSupported() { return File.isSupported() && FileList.isSupported() && FileReader.isSupported() && XMLHttpRequest2.isSupported(); } }