You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by re...@apache.org on 2023/04/14 08:50:53 UTC
[wicket] branch bugix/reiern70/WICKET-7037-master created (now 0b36a50428)
This is an automated email from the ASF dual-hosted git repository.
reiern70 pushed a change to branch bugix/reiern70/WICKET-7037-master
in repository https://gitbox.apache.org/repos/asf/wicket.git
at 0b36a50428 [WICKET-7033] implementation of: 1) an upload field that allows uploading files into to a mounted resource, 2) a reusable mounted resource that process uploads and 3) adapting upload progress bar to work in such use case
This branch includes the following new commits:
new 0b36a50428 [WICKET-7033] implementation of: 1) an upload field that allows uploading files into to a mounted resource, 2) a reusable mounted resource that process uploads and 3) adapting upload progress bar to work in such use case
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
[wicket] 01/01: [WICKET-7033] implementation of: 1) an upload field that allows uploading files into to a mounted resource, 2) a reusable mounted resource that process uploads and 3) adapting upload progress bar to work in such use case
Posted by re...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
reiern70 pushed a commit to branch bugix/reiern70/WICKET-7037-master
in repository https://gitbox.apache.org/repos/asf/wicket.git
commit 0b36a50428a417e933988435599f987f3c78452d
Author: reiern70 <re...@gmail.com>
AuthorDate: Thu Apr 6 10:20:25 2023 +0300
[WICKET-7033] implementation of: 1) an upload field that allows uploading files into to a mounted resource, 2) a reusable mounted resource that process uploads and 3) adapting upload progress bar to work in such use case
---
.../java/org/apache/wicket/ajax/AjaxUtils.java | 55 +++
.../resource/AbstractFileUploadResource.java | 223 +++++++++++
.../resource/FileUploadResourceReference.java | 132 ++++++
.../upload/resource/FileUploadToResourceField.java | 446 +++++++++++++++++++++
.../upload/resource/FileUploadToResourceField.js | 104 +++++
.../resource/FileUploadToResourceField.properties | 16 +
.../upload/resource/FolderUploadsFileManager.java | 83 ++++
.../form/upload/resource/IUploadsFileManager.java | 52 +++
.../wicket/examples/upload/UploadApplication.java | 19 +
.../apache/wicket/examples/upload/UploadPage.java | 5 +-
.../examples/upload/UploadToResourcePage.html | 44 ++
.../examples/upload/UploadToResourcePage.java | 263 ++++++++++++
.../apache/wicket/examples/homepage/HomePage.html | 1 +
.../resources/org/apache/wicket/examples/style.css | 2 +-
.../markup/html/form/upload/UploadProgressBar.java | 78 +++-
.../ajax/markup/html/form/upload/progressbar.js | 50 ++-
16 files changed, 1543 insertions(+), 30 deletions(-)
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/AjaxUtils.java b/wicket-core/src/main/java/org/apache/wicket/ajax/AjaxUtils.java
new file mode 100644
index 0000000000..7ed0253e1a
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/ajax/AjaxUtils.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.wicket.ajax;
+
+import java.util.Optional;
+import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.danekja.java.util.function.serializable.SerializableConsumer;
+
+/**
+ * Some AJAX related utility functions.
+ */
+public class AjaxUtils
+{
+
+ /**
+ * Runs action if current request is of type "AJAX". Otherwise, nothing is done.
+ *
+ * @param ajaxAction
+ * The action to run a {@link SerializableConsumer}
+ */
+ public static void executeIfAjax(SerializableConsumer<AjaxRequestTarget> ajaxAction)
+ {
+ Optional<AjaxRequestTarget> target = RequestCycle.get().find(AjaxRequestTarget.class);
+ target.ifPresent(ajaxAction);
+ }
+
+
+ /**
+ * Runs action if current request is of type "AJAX" or a Websockets request. Otherwise, nothing is done.
+ *
+ * @param ajaxAction
+ * The action to run a {@link SerializableConsumer}
+ */
+ public static void executeIfAjaxOrWebSockets(SerializableConsumer<IPartialPageRequestHandler> ajaxAction)
+ {
+ Optional<IPartialPageRequestHandler> target = RequestCycle.get().find(IPartialPageRequestHandler.class);
+ target.ifPresent(ajaxAction);
+ }
+
+}
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/AbstractFileUploadResource.java b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/AbstractFileUploadResource.java
new file mode 100644
index 0000000000..6bbce26384
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/AbstractFileUploadResource.java
@@ -0,0 +1,223 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.wicket.markup.html.form.upload.resource;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.fileupload2.FileItem;
+import org.apache.commons.fileupload2.pub.FileUploadSizeException;
+import org.apache.wicket.markup.html.form.upload.FileUpload;
+import org.apache.wicket.protocol.http.servlet.MultipartServletWebRequest;
+import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.resource.AbstractResource;
+import org.apache.wicket.util.lang.Bytes;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.github.openjson.JSONObject;
+import jakarta.servlet.http.HttpServletResponse;
+
+/**
+ * The resource that handles the file uploads.
+ * Reads the file items from the request parameters and uses {@link IUploadsFileManager}
+ * to store them.
+ * Additionally, cares about the response's content type and body.
+ * <p>
+ * This code was adapted from
+ * <p>
+ * <a href="https://github.com/martin-g/blogs/blob/master/file-upload/src/main/java/com/mycompany/fileupload/AbstractFileUploadResource.java">AbstractFileUploadResource.java</a>
+ * <p>
+ * The main difference is that there some JQuery plugin is used at client side (and it supports multiple uploads +
+ * some UI allowing to delete/preview files and so on).
+ * Here we are just using plain jQuery code at client side to upload a single file.
+ */
+public abstract class AbstractFileUploadResource extends AbstractResource
+{
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractFileUploadResource.class);
+
+ public static final String PARAM_NAME = "WICKET-FILE-UPLOAD";
+
+ /**
+ * This resource is usually an application singleton. Thus, client side pass
+ * to the resource a unique ID identifying the upload field performing the upload.
+ * The upload file makes sure this is a unique identifier at application level, See
+ * {@link FileUploadToResourceField#generateAUniqueApplicationWiseId()}. So that, there are no clashes between
+ * different users/pages/sessions performing an upload.
+ */
+ public static final String UPLOAD_ID = "uploadId";
+ /**
+ * i18n key for case no files were selected.
+ */
+ public static final String NO_FILE_SELECTED = "wicket.no.files.selected";
+ /**
+ * i18n key for the case selcted files exceed size limit.
+ */
+ public static final String REQUEST_SIZE_LIMIT_EXCEEDED = "wicket.multipart.size.exceeded";
+
+ private final IUploadsFileManager fileManager;
+
+ public AbstractFileUploadResource(IUploadsFileManager fileManager)
+ {
+ this.fileManager = fileManager;
+ }
+
+ /**
+ * Reads and stores the uploaded files
+ *
+ * @param attributes
+ * Attributes
+ * @return ResourceResponse
+ */
+ @Override
+ protected ResourceResponse newResourceResponse(Attributes attributes)
+ {
+ final ResourceResponse resourceResponse = new ResourceResponse();
+
+ final ServletWebRequest webRequest = (ServletWebRequest) attributes.getRequest();
+
+ // get the ID of the upload field (it should be unique per application)
+ String uploadId = webRequest.getRequestParameters().getParameterValue(UPLOAD_ID).toString("resource");
+
+ try
+ {
+ MultipartServletWebRequest multiPartRequest = webRequest.newMultipartWebRequest(getMaxSize(), uploadId);
+ multiPartRequest.parseFileParts();
+
+ RequestCycle.get().setRequest(multiPartRequest);
+
+ // retrieve the files.
+ Map<String, List<FileItem>> files = multiPartRequest.getFiles();
+ List<FileItem> fileItems = files.get(PARAM_NAME);
+
+ if (fileItems != null)
+ {
+ List<FileUpload> fileUploads = new ArrayList<>();
+ for (FileItem fileItem : fileItems)
+ {
+ fileUploads.add(new FileUpload(fileItem));
+ }
+ saveFiles(fileUploads, uploadId);
+ prepareResponse(resourceResponse, webRequest, fileUploads);
+ }
+ else
+ {
+ resourceResponse.setContentType("application/json");
+ resourceResponse.setWriteCallback(new WriteCallback()
+ {
+ @Override
+ public void writeData(Attributes attributes) throws IOException
+ {
+ JSONObject json = new JSONObject();
+ json.put("error", true);
+ json.put("errorMessage", NO_FILE_SELECTED);
+ String error = json.toString();
+ attributes.getResponse().write(error);
+ }
+ });
+ }
+
+ }
+ catch (Exception fux)
+ {
+ LOG.error("An error occurred while uploading a file", fux);
+ if (fux instanceof FileUploadSizeException)
+ {
+ resourceResponse.setStatusCode(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE);
+ return resourceResponse;
+ }
+ resourceResponse.setContentType("application/json");
+ JSONObject json = new JSONObject();
+ json.put("error", true);
+ String errorMessage = LOG.isDebugEnabled() ? fux.getMessage() : "An error occurred while uploading files";
+ json.put("errorMessage", errorMessage);
+ String error = json.toString();
+ resourceResponse.setWriteCallback(new WriteCallback()
+ {
+ @Override
+ public void writeData(Attributes attributes) throws IOException
+ {
+ attributes.getResponse().write(error);
+ }
+ });
+ }
+
+ return resourceResponse;
+ }
+
+ /**
+ * Sets the response's content type and body
+ *
+ * @param resourceResponse
+ * ResourceResponse
+ * @param webRequest
+ * ServletWebRequest
+ * @param fileItems
+ * List<FileUpload>
+ */
+ protected void prepareResponse(ResourceResponse resourceResponse, ServletWebRequest webRequest, List<FileUpload> fileItems)
+ {
+ resourceResponse.setContentType("application/json");
+ final String responseContent = generateJsonResponse(resourceResponse, webRequest, fileItems);
+
+ resourceResponse.setWriteCallback(new WriteCallback()
+ {
+ @Override
+ public void writeData(Attributes attributes) throws IOException
+ {
+ attributes.getResponse().write(responseContent);
+ }
+ });
+ }
+
+ /**
+ * Delegates to FileManager to store the uploaded files
+ *
+ * @param fileItems
+ * List<FileUpload>
+ */
+ protected void saveFiles(List<FileUpload> fileItems, String uploadId)
+ {
+ for (FileUpload fileItem : fileItems)
+ {
+ fileManager.save(fileItem, uploadId);
+ }
+ }
+
+ /**
+ * Defines what is the maximum size of the uploaded files.
+ *
+ * @return Bytes
+ */
+ protected abstract Bytes getMaxSize();
+
+ /**
+ * Should generate the response's body in JSON format
+ *
+ * @param resourceResponse
+ * ResourceResponse
+ * @param webRequest
+ * ServletWebRequest
+ * @param files
+ * List<FileUpload>
+ * @return The generated JSON
+ */
+ protected abstract String generateJsonResponse(ResourceResponse resourceResponse,
+ ServletWebRequest webRequest, List<FileUpload> files);
+
+}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileUploadResourceReference.java b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileUploadResourceReference.java
new file mode 100644
index 0000000000..802ea5df88
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileUploadResourceReference.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.wicket.markup.html.form.upload.resource;
+
+import java.util.List;
+import org.apache.wicket.markup.html.form.upload.FileUpload;
+import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
+import org.apache.wicket.request.resource.IResource;
+import org.apache.wicket.request.resource.ResourceReference;
+import org.apache.wicket.util.lang.Args;
+import org.apache.wicket.util.lang.Bytes;
+import org.danekja.java.util.function.serializable.SerializableSupplier;
+import com.github.openjson.JSONArray;
+import com.github.openjson.JSONException;
+import com.github.openjson.JSONObject;
+
+/**
+ * A resource reference that provides default implementation of AbstractFileUploadResource.
+ * The implementation generates JSON response with data from the upload (this data is
+ * re-routed to the page for things like getting the client file name and file size).
+ */
+public class FileUploadResourceReference extends ResourceReference
+{
+
+ private final IUploadsFileManager uploadFileManager;
+
+ private static FileUploadResourceReference instance;
+
+ /**
+ * This method assumes {@link #createNewInstance(IUploadsFileManager, SerializableSupplier)} was called before
+ *
+ * @return FileUploadResourceReference
+ */
+ public static FileUploadResourceReference getInstance()
+ {
+ if (instance == null)
+ {
+ throw new IllegalStateException("An instance should be created via the createNewInstance method");
+ }
+ return instance;
+ }
+
+ /**
+ * Use this method in order to create an instance to be mounted at application level.
+ *
+ * @param fileManager The {@link IUploadsFileManager}
+ * @param maxSize The maxSize of the multipart request.
+ * @return FileUploadResourceReference
+ */
+ public static FileUploadResourceReference createNewInstance(IUploadsFileManager fileManager, SerializableSupplier<Bytes> maxSize)
+ {
+ if (instance == null)
+ {
+ instance = new FileUploadResourceReference(fileManager, maxSize);
+ }
+ return instance;
+ }
+
+ private final SerializableSupplier<Bytes> maxSize;
+
+ protected FileUploadResourceReference(IUploadsFileManager uploadFileManager, SerializableSupplier<Bytes> maxSize)
+ {
+ super(FileUploadResourceReference.class, "file-uploads");
+
+ Args.notNull(uploadFileManager, "uploadFileManager");
+
+ this.uploadFileManager = uploadFileManager;
+ this.maxSize = maxSize;
+ }
+
+ @Override
+ public IResource getResource()
+ {
+ return new AbstractFileUploadResource(uploadFileManager)
+ {
+ @Override
+ protected Bytes getMaxSize()
+ {
+ return maxSize.get();
+ }
+
+ @Override
+ protected String generateJsonResponse(ResourceResponse resourceResponse, ServletWebRequest webRequest, List<FileUpload> files)
+ {
+ JSONArray json = new JSONArray();
+
+ for (FileUpload fileItem : files)
+ {
+ JSONObject fileJson = new JSONObject();
+
+ try
+ {
+ generateFileInfo(fileJson, fileItem);
+ json.put(fileJson);
+ }
+ catch (JSONException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ return json.toString();
+ }
+
+ };
+ }
+
+ public IUploadsFileManager getUploadFileManager() {
+ return uploadFileManager;
+ }
+
+ protected void generateFileInfo(JSONObject fileJson, FileUpload fileItem)
+ {
+ fileJson.put("clientFileName", fileItem.getClientFileName());
+ fileJson.put("size", fileItem.getSize());
+ fileJson.put("contentType", fileItem.getContentType());
+ }
+
+}
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileUploadToResourceField.java b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileUploadToResourceField.java
new file mode 100644
index 0000000000..9f1054fbaf
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileUploadToResourceField.java
@@ -0,0 +1,446 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.wicket.markup.html.form.upload.resource;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+import org.apache.commons.io.IOUtils;
+import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.AjaxUtils;
+import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.JavaScriptHeaderItem;
+import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
+import org.apache.wicket.markup.html.form.upload.FileUpload;
+import org.apache.wicket.markup.html.form.upload.FileUploadField;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.request.Request;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.request.resource.JavaScriptResourceReference;
+import org.apache.wicket.resource.CoreLibrariesContributor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.github.openjson.JSONArray;
+import com.github.openjson.JSONObject;
+
+/**
+ * Implementation of FileUploadField capable to uploading a file into a wicket mounted resource.
+ * This field does not require a {@link org.apache.wicket.markup.html.form.Form}!
+ * The upload of the file id done via the {@link #startUpload(IPartialPageRequestHandler)} method.
+ */
+public abstract class FileUploadToResourceField extends FileUploadField
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(FileUploadToResourceField.class);
+
+ /**
+ * Info regarding an upload.
+ */
+ public static final class UploadInfo
+ {
+ private File file;
+ private final String clientFileName;
+
+ private final long size;
+
+ private final String contentType;
+
+ public UploadInfo(String clientFileName, long size, String contentType)
+ {
+ this.clientFileName = clientFileName;
+ this.size = size;
+ this.contentType = contentType;
+ }
+
+ public File getFile()
+ {
+ return file;
+ }
+
+ public void setFile(File file)
+ {
+ this.file = file;
+ }
+
+ public String getClientFileName()
+ {
+ return clientFileName;
+ }
+
+ public long getSize()
+ {
+ return size;
+ }
+
+ public String getContentType()
+ {
+ return contentType;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ UploadInfo fileInfo = (UploadInfo) o;
+ return Objects.equals(clientFileName, fileInfo.clientFileName);
+ }
+
+ /**
+ * @return the bytes associated with the upload. Return null in case no file is present
+ * or some other error condition happens.
+ */
+ public byte[] get()
+ {
+ if (file == null)
+ {
+ return null;
+ }
+
+ byte[] fileData = new byte[(int) getSize()];
+ InputStream fis = null;
+
+ try
+ {
+ fis = new FileInputStream(file);
+ IOUtils.readFully(fis, fileData);
+ }
+ catch (IOException e)
+ {
+ LOGGER.debug("IOException at get", e);
+ fileData = null;
+ }
+ finally
+ {
+ IOUtils.closeQuietly(fis);
+ }
+
+ return fileData;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(clientFileName);
+ }
+
+ public static List<UploadInfo> fromJson(String json)
+ {
+ List<UploadInfo> infos = new ArrayList<>();
+ JSONArray jsonArray = new JSONArray(json);
+ for (int i = 0; i < jsonArray.length(); i++)
+ {
+ JSONObject jsonObject = jsonArray.getJSONObject(i);
+ infos.add(new UploadInfo(jsonObject.getString("clientFileName"), jsonObject.getLong("size"), jsonObject.getString("contentType")));
+ }
+ return infos;
+ }
+ }
+
+ private static final JavaScriptResourceReference JS = new JavaScriptResourceReference(FileUploadToResourceField.class, "FileUploadToResourceField.js");
+
+ public static String UPLOAD_CANCELED = "upload.canceled";
+
+
+ private static abstract class FileModel implements IModel<List<UploadInfo>>
+ {
+
+ @Override
+ public List<UploadInfo> getObject()
+ {
+ List<UploadInfo> fileInfos = getFileUploadInfos();
+ for (UploadInfo uploadInfo : fileInfos)
+ {
+ // at this point files were stored at the server side by resource
+ // UploadFieldId acts as a discriminator at application level
+ // so that uploaded files are isolated.
+ uploadInfo.setFile(fileManager().getFile(getUploadFieldId(), uploadInfo.clientFileName));
+ }
+ return fileInfos;
+ }
+
+ @Override
+ public void setObject(List<UploadInfo> object)
+ {
+ throw new UnsupportedOperationException("setObject not supported");
+ }
+
+ protected abstract IUploadsFileManager fileManager();
+
+ /*
+ This is an application unique ID assigned to upload field.
+ */
+ protected abstract String getUploadFieldId();
+
+ protected abstract List<UploadInfo> getFileUploadInfos();
+ }
+
+ private final AbstractDefaultAjaxBehavior ajaxBehavior;
+
+ private transient List<UploadInfo> fileUploadInfos;
+
+ public FileUploadToResourceField(String id) {
+ super(id);
+ setOutputMarkupId(true);
+ // generate a unique ID
+ setMarkupId(generateAUniqueApplicationWiseId());
+ setDefaultModel(new FileModel() {
+ @Override
+ protected IUploadsFileManager fileManager() {
+ return FileUploadToResourceField.this.fileManager();
+ }
+
+ @Override
+ protected String getUploadFieldId()
+ {
+ return FileUploadToResourceField.this.getMarkupId();
+ }
+
+ @Override
+ protected List<UploadInfo> getFileUploadInfos()
+ {
+ return fileUploadInfos;
+ }
+ });
+ ajaxBehavior = new AbstractDefaultAjaxBehavior()
+ {
+ @Override
+ protected void respond(AjaxRequestTarget target)
+ {
+ Request request = RequestCycle.get().getRequest();
+ boolean error = request.getRequestParameters().getParameterValue("error").toBoolean(true);
+ if (!error) {
+ String filesIfo = request.getRequestParameters().getParameterValue("filesInfo").toString();
+ fileUploadInfos = UploadInfo.fromJson(filesIfo);
+ onUploadSuccess(target, getFileUploadInfos());
+ }
+ else
+ {
+ String errorMessage = request.getRequestParameters().getParameterValue("errorMessage").toString(null);
+ if (UPLOAD_CANCELED.equals(errorMessage))
+ {
+ onUploadCanceled(target);
+ }
+ else
+ {
+ if (AbstractFileUploadResource.NO_FILE_SELECTED.equals(errorMessage))
+ {
+ errorMessage = getString(AbstractFileUploadResource.NO_FILE_SELECTED);
+ }
+ else if (isUploadTooBig(errorMessage))
+ {
+ errorMessage = getString(AbstractFileUploadResource.REQUEST_SIZE_LIMIT_EXCEEDED);
+ }
+ onUploadFailure(target, errorMessage);
+ }
+ }
+ }
+ };
+ add(ajaxBehavior);
+ }
+
+ /**
+ *
+ * @param errorMessage The error message
+ * @return true iff errorMessage is because upload exceeded max allowed size.
+ */
+ protected boolean isUploadTooBig(String errorMessage)
+ {
+ return "Payload Too Large".equals(errorMessage);
+ }
+
+ private List<UploadInfo> getFileUploadInfos()
+ {
+ // mind that this makes files to be added.
+ return (List<UploadInfo>)getDefaultModel().getObject();
+ }
+
+ @Override
+ public FileUpload getFileUpload()
+ {
+ throw new UnsupportedOperationException("FileUploadToResourceField does not support working with FileUpload");
+ }
+
+ @Override
+ public List<FileUpload> getFileUploads()
+ {
+ throw new UnsupportedOperationException("FileUploadToResourceField does not support working with FileUpload");
+ }
+
+ @Override
+ protected void onRemove()
+ {
+ super.onRemove();
+ // we clean any client side mess if component is removed via "partial" page replacement
+ AjaxUtils.executeIfAjaxOrWebSockets(target -> target.appendJavaScript("delete Wicket.Timer." + getMarkupId() + ";"));
+
+ }
+
+ /**
+ * Override to do something on a successful upload.
+ *
+ * @param target The {@link AjaxRequestTarget}
+ * @param fileInfos The List<FileInfo
+ */
+ protected abstract void onUploadSuccess(AjaxRequestTarget target, List<UploadInfo> fileInfos);
+
+
+ /**
+ * Override to do something on a non successful upload
+ *
+ * @param target The {@link AjaxRequestTarget}
+ * @param errorInfo The cause of the failure
+ */
+ protected void onUploadFailure(AjaxRequestTarget target, String errorInfo)
+ {
+ // nothing by default
+ }
+
+ /**
+ * Override to do something in case user canceled upload
+ *
+ * @param target The {@link AjaxRequestTarget}
+ */
+ protected void onUploadCanceled(AjaxRequestTarget target)
+ {
+ // nothing by default
+ }
+
+ /**
+ * @return a unique application wise ID (it should be a valid HTML id).
+ */
+ protected String generateAUniqueApplicationWiseId()
+ {
+ return "WRFUF_" + UUID.randomUUID().toString().replace("-", "_");
+ }
+
+
+ @Override
+ public void renderHead(IHeaderResponse response)
+ {
+ CoreLibrariesContributor.contributeAjax(getApplication(), response);
+ response.render(JavaScriptHeaderItem.forReference(JS));
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.put("inputName", getMarkupId());
+ jsonObject.put("resourceUrl", urlFor(getFileUploadResourceReference(), new PageParameters()).toString());
+ jsonObject.put("ajaxCallBackUrl", ajaxBehavior.getCallbackUrl());
+ response.render(OnDomReadyHeaderItem.forScript("Wicket.Timer."
+ + getMarkupId() + " = new Wicket.FileUploadToResourceField("
+ + jsonObject + ","
+ + getClientBeforeSendCallBack() + ","
+ + getClientSideSuccessCallBack() + ","
+ + getClientSideCancelCallBack() + ","
+ + getClientSideUploadErrorCallBack() + ");"));
+ }
+
+ /**
+ * Override if you need to return a different instance of FileUploadResourceReference
+ * @return FileUploadResourceReference
+ */
+ protected FileUploadResourceReference getFileUploadResourceReference()
+ {
+ return FileUploadResourceReference.getInstance();
+ }
+
+ /**
+ * @return The JavaScript expression starting the upload.
+ */
+ public String getTriggerUploadScript()
+ {
+ return "Wicket.Timer." + getMarkupId() + ".upload();";
+ }
+
+ /**
+ * Starts the upload via an AJAX request.
+ *
+ * @param target The {@link AjaxRequestTarget}
+ */
+ public void startUpload(IPartialPageRequestHandler target)
+ {
+ target.appendJavaScript(getTriggerUploadScript());
+ }
+
+ /**
+ * @return The JavaScript expression canceling the upload.
+ */
+ public String getTriggerCancelUploadScript()
+ {
+ return "Wicket.Timer." + getMarkupId() + ".cancel();";
+ }
+ /**
+ * Cancels the upload via an AJAX request.
+ *
+ * @param target The {@link AjaxRequestTarget}
+ */
+ public void cancelUpload(IPartialPageRequestHandler target)
+ {
+ target.appendJavaScript(getTriggerCancelUploadScript());
+ }
+
+ /**
+ * See jQuery.ajax documentation.
+ *
+ * @return A JavaScript function to be executed on beforeSend the upload request.
+ */
+ protected CharSequence getClientBeforeSendCallBack()
+ {
+ return "function (xhr, settings) { return true; }";
+ }
+
+ /**
+ * @return A JavaScript function to be executed on successful upload. This is, besides the normal wicket
+ * AJAX request (see {@link #onUploadSuccess(AjaxRequestTarget, List)}).
+ */
+ protected CharSequence getClientSideSuccessCallBack()
+ {
+ return "function () {}";
+ }
+
+ /**
+ * @return A JavaScript function to be executed on upload canceled. This is, besides the normal wicket
+ * AJAX request (see {@link #onUploadFailure(AjaxRequestTarget, String)}).
+ */
+ protected CharSequence getClientSideCancelCallBack()
+ {
+ return "function () {}";
+ }
+
+ /**
+ * @return A JavaScript function to be executed on upload canceled. This is, besides the normal wicket
+ * AJAX request (see {@link #onUploadFailure(AjaxRequestTarget, String)}). It receives as parameter a JSON object
+ * like <code>{'error': true, errorMessage: 'xxx'}</code>.
+ */
+ protected CharSequence getClientSideUploadErrorCallBack()
+ {
+ return "function (res) {}";
+ }
+
+ /**
+ * @return The IUploadsFileManager
+ */
+ protected IUploadsFileManager fileManager()
+ {
+ return getFileUploadResourceReference().getUploadFileManager();
+ }
+}
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileUploadToResourceField.js b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileUploadToResourceField.js
new file mode 100644
index 0000000000..05a5349167
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileUploadToResourceField.js
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+;(function (undefined) {
+
+ 'use strict';
+
+ if (typeof(Wicket.FileUploadToResourceField) === 'object') {
+ return;
+ }
+
+ Wicket.FileUploadToResourceField = function (settings, clientBeforeSendCallBack, clientSideSuccessCallBack, clientSideCancelCallBack, uploadErrorCallBack)
+ {
+ this.inputName = settings.inputName;
+ this.input = document.getElementById(this.inputName);
+ this.resourceUrl = settings.resourceUrl + "?uploadId=" + this.inputName;
+ this.ajaxCallBackUrl = settings.ajaxCallBackUrl;
+ this.clientBeforeSendCallBack = clientBeforeSendCallBack;
+ this.clientSideSuccessCallBack = clientSideSuccessCallBack;
+ this.clientSideCancelCallBack = clientSideCancelCallBack;
+ this.uploadErrorCallBack = uploadErrorCallBack;
+ }
+
+ Wicket.FileUploadToResourceField.prototype.upload = function()
+ {
+ // get a fresh reference to input
+ this.input = document.getElementById(this.inputName);
+ // we add the files to a FormData object.
+ var formData = new FormData();
+ var totalfiles = this.input.files.length;
+ for (var index = 0; index < totalfiles; index++) {
+ formData.append("WICKET-FILE-UPLOAD",this.input.files[index]);
+ }
+ // pass the input name to know where to store files at server side.
+ formData.append("uploadId", this.inputName);
+ var self = this;
+ // we use jQuery to post the files to the resource (this.resourceUrl)
+ // and we keep a reference to the request in order to be able
+ // to cancel the upload
+ this.xhr = $.ajax({
+ url: this.resourceUrl,
+ type: "POST",
+ data: formData,
+ processData: false,
+ contentType: false,
+ success: function (res) {
+ // do clean up on success
+ if (res.error) {
+ Wicket.Ajax.get({"u": self.ajaxCallBackUrl, "ep": res});
+ self.uploadErrorCallBack(res);
+ } else {
+ self.clientSideSuccessCallBack();
+ var ep = {'error': false, 'filesInfo': JSON.stringify(res)};
+ Wicket.Ajax.get({"u": self.ajaxCallBackUrl, "ep": ep});
+ }
+ },
+ beforeSend: function (xhr) {
+ self.clientBeforeSendCallBack(xhr);
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ if (textStatus === "abort") {
+ // user aborted the upload.
+ var ep = {'error': true, 'errorMessage': 'upload.canceled'};
+ Wicket.Ajax.get({"u": self.ajaxCallBackUrl, "ep": ep});
+ } else if (textStatus === "error"){
+ var ep = {'error': true, "errorMessage": errorThrown};
+ self.uploadErrorCallBack(ep);
+ Wicket.Ajax.get({"u": self.ajaxCallBackUrl, "ep": ep});
+ } else if (textStatus === "parsererror"){
+ // this error will only happen is generated JSON at server side is faulty
+ var data = jqXHR.responseText;
+ Wicket.Log.log(data);
+ }
+ }
+ });
+ }
+
+ // cancel the upload
+ Wicket.FileUploadToResourceField.prototype.cancel = function () {
+ // we have a reference to the request we can cancel it.
+ if (this.xhr) {
+ this.xhr.abort();
+ this.clientSideCancelCallBack();
+ Wicket.Log.log("The upload associated with field '" + this.inputName + "' has been canceled!");
+ delete (this.xhr);
+ } else {
+ Wicket.Log.log("Too late to cancel upload for field '" + this.inputName + "': the upload has already finished.");
+ }
+ }
+})();
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileUploadToResourceField.properties b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileUploadToResourceField.properties
new file mode 100644
index 0000000000..4399cf5805
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileUploadToResourceField.properties
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+wicket.no.files.selected = Nothing to upload: no files were selected.
+wicket.multipart.size.exceeded = The total size of the selected files exceeds configured limits.
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FolderUploadsFileManager.java b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FolderUploadsFileManager.java
new file mode 100644
index 0000000000..0146b82099
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FolderUploadsFileManager.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.wicket.markup.html.form.upload.resource;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.markup.html.form.upload.FileUpload;
+import org.apache.wicket.util.file.File;
+import org.apache.wicket.util.io.IOUtils;
+import org.apache.wicket.util.lang.Args;
+
+/**
+ * Implementation of {@link IUploadsFileManager} that stores files in sub-folders of a given folder.
+ * Sub-folders are named using the input field ID (and those are unique per application). Thus, there
+ * is no possibility that uploads get mixed up.
+ */
+public class FolderUploadsFileManager implements IUploadsFileManager
+{
+
+ private final File folder;
+
+ public FolderUploadsFileManager(File folder)
+ {
+ Args.notNull(folder, "folder");
+ if (!folder.exists())
+ {
+ try
+ {
+ Files.createDirectories(folder.toPath());
+ }
+ catch (IOException e)
+ {
+ throw new WicketRuntimeException(e);
+ }
+ }
+ else if (folder.exists() && !folder.isDirectory())
+ {
+ throw new IllegalArgumentException("Not a folder : " + folder.getAbsolutePath());
+ }
+ this.folder = folder;
+ }
+
+ public File getFolder() {
+ return folder;
+ }
+
+ @Override
+ public void save(FileUpload fileItem, String uploadFieldId)
+ {
+ File uploadFieldFolder = new File(getFolder(), uploadFieldId);
+ uploadFieldFolder.mkdirs();
+ try
+ {
+ IOUtils.copy(fileItem.getInputStream(), new FileOutputStream(new File(uploadFieldFolder, fileItem.getClientFileName())));
+ }
+ catch (IOException e)
+ {
+ throw new WicketRuntimeException(e);
+ }
+ }
+
+ @Override
+ public File getFile(String uploadFieldId, String clientFileName)
+ {
+ return new File(new File(getFolder(), uploadFieldId), clientFileName);
+ }
+}
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/IUploadsFileManager.java b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/IUploadsFileManager.java
new file mode 100644
index 0000000000..78a1208503
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/IUploadsFileManager.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.wicket.markup.html.form.upload.resource;
+
+import org.apache.wicket.markup.html.form.upload.FileUpload;
+import org.apache.wicket.util.file.File;
+
+/**
+ * This interface defines the bridge between a file uploaded to a resource and the wicket component.
+ * Wicket component uses some identifier (passed as a request parameter) to instruct the resource
+ * how to store file. Same identifier is used to retrieve the file, once uploaded, from component in page.
+ * Mind that uploader resource is a singleton => identifier needs to be unique among different sessions
+ * (and pages in a session).
+ */
+public interface IUploadsFileManager
+{
+
+ /**
+ * Saves an uploaded files into some persistent storage (e,g, disk).
+ *
+ * @param fileItem
+ * The {@link FileUpload}
+ * @param uploadFieldId
+ * The unique ID of the upload field.
+ */
+ void save(FileUpload fileItem, String uploadFieldId);
+
+ /**
+ * Retrieves the file based on an uploadFieldId and clientFileName.
+ *
+ * @param uploadFieldId
+ * The unique ID of the upload field.
+ * @param clientFileName
+ * The client file name
+ * @return File the file
+ */
+ File getFile(String uploadFieldId, String clientFileName);
+}
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadApplication.java b/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadApplication.java
index 0826393842..f6c5054c28 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadApplication.java
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadApplication.java
@@ -18,7 +18,11 @@ package org.apache.wicket.examples.upload;
import org.apache.wicket.Page;
import org.apache.wicket.examples.WicketExampleApplication;
+import org.apache.wicket.markup.html.form.upload.resource.FileUploadResourceReference;
+import org.apache.wicket.markup.html.form.upload.resource.FolderUploadsFileManager;
+import org.apache.wicket.markup.html.form.upload.resource.IUploadsFileManager;
import org.apache.wicket.util.file.Folder;
+import org.apache.wicket.util.lang.Bytes;
/**
@@ -30,6 +34,8 @@ public class UploadApplication extends WicketExampleApplication
{
private Folder uploadFolder = null;
+ private IUploadsFileManager uploadsFileManager;
+
@Override
public Class<? extends Page> getHomePage()
{
@@ -55,9 +61,22 @@ public class UploadApplication extends WicketExampleApplication
// Ensure folder exists
uploadFolder.mkdirs();
+ uploadsFileManager = new FolderUploadsFileManager(uploadFolder);
+
+ mountResource("/uploads", FileUploadResourceReference.createNewInstance(uploadsFileManager, ()-> Bytes.kilobytes(100)));
+
mountPage("/multi", MultiUploadPage.class);
mountPage("/single", UploadPage.class);
+ mountPage("/uploadToResource", UploadToResourcePage.class);
getApplicationSettings().setUploadProgressUpdatesEnabled(true);
}
+
+ public IUploadsFileManager getUploadsFileManager() {
+ return uploadsFileManager;
+ }
+
+ public static UploadApplication getInstance() {
+ return (UploadApplication)get();
+ }
}
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadPage.java b/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadPage.java
index 3fe09b7eef..2ad1498e6b 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadPage.java
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadPage.java
@@ -20,7 +20,6 @@ import java.io.File;
import java.util.Arrays;
import java.util.List;
-import org.apache.wicket.Application;
import org.apache.wicket.examples.WicketExamplePage;
import org.apache.wicket.extensions.ajax.markup.html.form.upload.UploadProgressBar;
import org.apache.wicket.markup.html.basic.Label;
@@ -173,7 +172,7 @@ public class UploadPage extends WicketExamplePage
// Add folder view
add(new Label("dir", uploadFolder.getAbsolutePath()));
- fileListView = new FileListView("fileList", new LoadableDetachableModel<List<File>>()
+ fileListView = new FileListView("fileList", new LoadableDetachableModel<>()
{
@Override
protected List<File> load()
@@ -216,6 +215,6 @@ public class UploadPage extends WicketExamplePage
private Folder getUploadFolder()
{
- return ((UploadApplication)Application.get()).getUploadFolder();
+ return UploadApplication.getInstance().getUploadFolder();
}
}
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadToResourcePage.html b/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadToResourcePage.html
new file mode 100644
index 0000000000..ba2097cddf
--- /dev/null
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadToResourcePage.html
@@ -0,0 +1,44 @@
+<html xmlns:wicket="http://wicket.apache.org">
+<head>
+ <title>Wicket Examples - upload</title>
+ <style>
+ legend { border: 1px solid #e9601a; background-color: #bbb; color: #fff; padding: 4px;}
+ fieldset { border: 1px solid #e9601a; padding: 10px; margin-top: 10px;}
+ </style>
+</head>
+<body>
+ <wicket:extend>
+ <p>Wicket can upload to a <em>mounted resource.</em></p>
+ <p>This upload is done via jQuery AJAX and <em>does not</em> block Wicket AJAX</p>
+ <p><em>Upload progressbar</em> has been updated to work with this component</p>
+ <br/>
+
+ <div>
+ <fieldset>
+ <legend>Upload a file(s) to a <em>mounted resource</em></legend>
+ <p>
+ <label wicket:for="fileInput">File</label>
+ <input wicket:id="fileInput" type="file" multiple="multiple"/>
+ </p>
+ <form wicket:id="form">
+ <p>
+ <input wicket:id="allowToLeavePageWhileUploading" type="checkbox"/>
+ <label wicket:for="allowToLeavePageWhileUploading">Block leaving page while upload is happening?</label>
+ </p>
+ </form>
+ <div>
+ <span wicket:id="progress">[[upload progressbar]]</span>
+ </div>
+ <input wicket:id="upload" type="button" value="Upload to a resource"/>
+ <input wicket:id="cancelUpload" type="button" value="Cancel upload via AJAX request"/>
+ <input wicket:id="cancelUploadClientSide" type="button" value="Cancel upload at client side"/>
+ <button wicket:id="counter" type="button">Click me</button>
+ </fieldset>
+ </div>
+ <br/>
+ <div>
+ <span wicket:id="uploadFeedback"/>
+ </div>
+ </wicket:extend>
+</body>
+</html>
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadToResourcePage.java b/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadToResourcePage.java
new file mode 100644
index 0000000000..e9e43e9872
--- /dev/null
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadToResourcePage.java
@@ -0,0 +1,263 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.wicket.examples.upload;
+
+import java.util.List;
+import org.apache.wicket.Component;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import org.apache.wicket.ajax.markup.html.form.AjaxCheckBox;
+import org.apache.wicket.behavior.Behavior;
+import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
+import org.apache.wicket.event.IEvent;
+import org.apache.wicket.examples.WicketExamplePage;
+import org.apache.wicket.extensions.ajax.markup.html.form.upload.UploadProgressBar;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
+import org.apache.wicket.markup.head.OnEventHeaderItem;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.upload.resource.IUploadsFileManager;
+import org.apache.wicket.markup.html.form.upload.resource.FileUploadToResourceField;
+import org.apache.wicket.markup.html.panel.FeedbackPanel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+
+/**
+ * Upload example.
+ *
+ * @author reiern70
+ */
+public class UploadToResourcePage extends WicketExamplePage
+{
+ private static class UpdateInEachAjaxRequestBehavior extends Behavior
+ {
+
+ @Override
+ public void bind(Component component)
+ {
+ component.setOutputMarkupPlaceholderTag(true);
+ }
+
+ @Override
+ public void onEvent(Component component, IEvent<?> event)
+ {
+ if (event.getPayload() instanceof AjaxRequestTarget)
+ {
+ ((AjaxRequestTarget)event.getPayload()).add(component);
+ }
+ }
+ }
+
+ private final FeedbackPanel uploadFeedback;
+
+ private final FileUploadToResourceField fileUploadToResourceField;
+
+ private boolean uploading;
+
+ private int counter;
+
+ private final IModel<Boolean> allowToLeavePageWhileUploading = Model.of(true);
+
+ /**
+ * Constructor.
+ *
+ * @param parameters
+ * Page parameters
+ */
+ public UploadToResourcePage(final PageParameters parameters)
+ {
+ fileUploadToResourceField = new FileUploadToResourceField("fileInput")
+ {
+
+ private void updateFeedback(AjaxRequestTarget target)
+ {
+ target.add(uploadFeedback);
+ uploading = false;
+ }
+
+ @Override
+ protected void onUploadSuccess(AjaxRequestTarget target, List<UploadInfo> infos)
+ {
+ for (UploadInfo uploadInfo : infos) {
+ info("File " + uploadInfo.getClientFileName() + " successfully uploaded. Size: " + uploadInfo.getSize() + "bytes. ContentType: "
+ + uploadInfo.getContentType() +". Stored at " + uploadInfo.getFile().getAbsolutePath());
+ }
+ updateFeedback(target);
+ }
+
+ @Override
+ protected void onUploadFailure(AjaxRequestTarget target, String errorInfo)
+ {
+ error("Upload failed with error: " + errorInfo);
+ updateFeedback(target);
+ }
+
+ @Override
+ public void startUpload(IPartialPageRequestHandler target) {
+ if (allowToLeavePageWhileUploading.getObject())
+ {
+ target.prependJavaScript("Wicket.CurrentUpload = {};");
+ }
+ super.startUpload(target);
+ }
+
+ @Override
+ protected void onUploadCanceled(AjaxRequestTarget target)
+ {
+ info("You have canceled the upload!");
+ updateFeedback(target);
+ }
+
+ @Override
+ protected CharSequence getClientSideSuccessCallBack() {
+ return "function() {Wicket.CurrentUpload = null}";
+ }
+
+ @Override
+ protected CharSequence getClientSideUploadErrorCallBack()
+ {
+ return "function() {Wicket.CurrentUpload = null}";
+ }
+
+ @Override
+ protected String getClientSideCancelCallBack()
+ {
+ return "function() {Wicket.CurrentUpload = null}";
+ }
+
+ @Override
+ protected IUploadsFileManager fileManager()
+ {
+ return getUploadsFileManager();
+ }
+ };
+ add(fileUploadToResourceField);
+
+ Form<Void> form = new Form<>("form");
+ add(form);
+ form.add(new AjaxCheckBox("allowToLeavePageWhileUploading", allowToLeavePageWhileUploading) {
+ @Override
+ protected void onUpdate(AjaxRequestTarget target) {
+
+ }
+ });
+ final UploadProgressBar uploadProgressBar = new UploadProgressBar("progress", fileUploadToResourceField)
+ {
+
+ @Override
+ protected String getOnProgressUpdatedCallBack()
+ {
+ return "function(percent) { console.log(percent);}";
+ }
+ };
+ add(uploadProgressBar);
+ add(new AjaxLink<Void>("upload")
+ {
+ @Override
+ public void onClick(AjaxRequestTarget target)
+ {
+ uploading = true;
+ fileUploadToResourceField.startUpload(target);
+ uploadProgressBar.start(target);
+ target.add(uploadFeedback);
+ }
+
+ @Override
+ protected void onConfigure()
+ {
+ super.onConfigure();
+ setVisible(!uploading);
+ }
+ }.add(new UpdateInEachAjaxRequestBehavior()));
+
+ add(new AjaxLink<Void>("cancelUpload")
+ {
+ @Override
+ public void onClick(AjaxRequestTarget target)
+ {
+ fileUploadToResourceField.cancelUpload(target);
+ target.add(uploadFeedback);
+ }
+
+ @Override
+ protected void onConfigure()
+ {
+ super.onConfigure();
+ setVisible(uploading);
+ }
+ }.add(new UpdateInEachAjaxRequestBehavior()));
+
+
+ add(new WebMarkupContainer("cancelUploadClientSide")
+ {
+
+ @Override
+ public void renderHead(IHeaderResponse response) {
+ super.renderHead(response);
+ response.render(OnEventHeaderItem.forComponent(this, "click", fileUploadToResourceField.getTriggerCancelUploadScript()));
+ }
+
+ @Override
+ protected void onConfigure()
+ {
+ super.onConfigure();
+ setVisible(uploading);
+ }
+
+ }.add(new UpdateInEachAjaxRequestBehavior()));
+
+ add(new AjaxLink<Void>("counter")
+ {
+
+ @Override
+ public void onClick(AjaxRequestTarget target)
+ {
+ counter ++;
+ target.add(this);
+ }
+
+ }.setBody(()-> "Click me: upload does not blocks normal wicket AJAX. I was clicked " + counter).setOutputMarkupId(true));
+ // Create feedback panels
+ uploadFeedback = new FeedbackPanel("uploadFeedback");
+ uploadFeedback.setOutputMarkupId(true);
+
+ // Add uploadFeedback to the page itself
+ add(uploadFeedback);
+
+ }
+
+ @Override
+ public void renderHead(IHeaderResponse response) {
+ super.renderHead(response);
+ response.render(
+ OnDomReadyHeaderItem.forScript("$(window).bind('beforeunload', function(){ if (Wicket.CurrentUpload != null) { return 'leave?'; }});"));
+ }
+
+ @Override
+ protected void onBeforeRender()
+ {
+ super.onBeforeRender();
+ uploading = false;
+ }
+
+ private IUploadsFileManager getUploadsFileManager()
+ {
+ return UploadApplication.getInstance().getUploadsFileManager();
+ }
+}
diff --git a/wicket-examples/src/main/resources/org/apache/wicket/examples/homepage/HomePage.html b/wicket-examples/src/main/resources/org/apache/wicket/examples/homepage/HomePage.html
index 46631905f3..4c339f81c5 100644
--- a/wicket-examples/src/main/resources/org/apache/wicket/examples/homepage/HomePage.html
+++ b/wicket-examples/src/main/resources/org/apache/wicket/examples/homepage/HomePage.html
@@ -23,6 +23,7 @@
<tr><td align="right"><a href="authorization">authorization</a></td><td> - Demonstrates authorization for pages and components.</td></tr>
<tr><td align="right"><a href="upload/single">upload</a></td><td> - Single file upload.</td></tr>
<tr><td align="right"><a href="upload/multi">upload</a></td><td> - Multiple file upload.</td></tr>
+ <tr><td align="right"><a href="upload/singleUploadToResource">upload</a></td><td> - Single AJAX file upload to a resource.</td></tr>
<tr><td align="right"><a href="template">template</a></td><td> - Templating example.</td></tr>
<tr><td align="right"><a href="mailtemplate">mail template</a></td><td> - Generate mail templates out of Page, Panel or TextTemplate.</td></tr>
<tr><td align="right"><a href="stateless">stateless</a></td><td> - Demonstrates stateless pages/sessions.</td></tr>
diff --git a/wicket-examples/src/main/resources/org/apache/wicket/examples/style.css b/wicket-examples/src/main/resources/org/apache/wicket/examples/style.css
index f27f2c2dac..81fcf9216f 100644
--- a/wicket-examples/src/main/resources/org/apache/wicket/examples/style.css
+++ b/wicket-examples/src/main/resources/org/apache/wicket/examples/style.css
@@ -305,7 +305,7 @@ button.button--alert {
input[type=submit],
input[type=reset],
input[type=button],
-button {
+button, a.button {
background: #FF9925;
color: #fff; }
.button:hover,
diff --git a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/form/upload/UploadProgressBar.java b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/form/upload/UploadProgressBar.java
index d938e370e3..2b7e4aeca2 100644
--- a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/form/upload/UploadProgressBar.java
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/form/upload/UploadProgressBar.java
@@ -21,6 +21,7 @@ import java.util.Formatter;
import org.apache.wicket.Application;
import org.apache.wicket.IInitializer;
import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
import org.apache.wicket.markup.head.CssHeaderItem;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
@@ -29,7 +30,6 @@ import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.upload.FileUploadField;
import org.apache.wicket.markup.html.panel.Panel;
-import org.apache.wicket.model.IModel;
import org.apache.wicket.model.StringResourceModel;
import org.apache.wicket.request.resource.CssResourceReference;
import org.apache.wicket.request.resource.JavaScriptResourceReference;
@@ -109,7 +109,7 @@ public class UploadProgressBar extends Panel
private static final long serialVersionUID = 1L;
- private final Form<?> form;
+ private Form<?> form;
private MarkupContainer statusDiv;
@@ -117,6 +117,30 @@ public class UploadProgressBar extends Panel
private final FileUploadField uploadField;
+ /**
+ * Constructor that will display the upload progress bar for every submit of the given form.
+ *
+ * @param id
+ * component id (not null)
+ * @param uploadField
+ * the file upload field to check for a file upload, or null to display the upload
+ * field for every submit of the given form
+ */
+ public UploadProgressBar(final String id, final FileUploadField uploadField)
+ {
+ super(id);
+
+ this.uploadField = uploadField;
+ if (uploadField != null)
+ {
+ uploadField.setOutputMarkupId(true);
+ }
+
+ setRenderBodyOnly(true);
+ }
+
+
+
/**
* Constructor that will display the upload progress bar for every submit of the given form.
*
@@ -163,7 +187,11 @@ public class UploadProgressBar extends Panel
protected void onInitialize()
{
super.onInitialize();
- getCallbackForm().setOutputMarkupId(true);
+ Form<?> form = getCallbackForm();
+ if (form != null)
+ {
+ form.setOutputMarkupId(true);
+ }
barDiv = newBarComponent("bar");
add(barDiv);
@@ -228,29 +256,53 @@ public class UploadProgressBar extends Panel
ResourceReference ref = new SharedResourceReference(RESOURCE_NAME);
- final String uploadFieldId = (uploadField == null) ? "" : uploadField.getMarkupId();
+ final String uploadFieldId = (uploadField == null) ? "null" : ("'" + uploadField.getMarkupId() + "'");
- final String status = new StringResourceModel(RESOURCE_STARTING, this, (IModel<?>)null).getString();
+ final String status = new StringResourceModel(RESOURCE_STARTING, this, null).getString();
- CharSequence url = urlFor(ref, UploadStatusResource.newParameter(getPage().getId()));
+ CharSequence url = form != null ? urlFor(ref, UploadStatusResource.newParameter(getPage().getId())) :
+ urlFor(ref, UploadStatusResource.newParameter(uploadField.getMarkupId()));
StringBuilder builder = new StringBuilder(128);
Formatter formatter = new Formatter(builder);
- formatter.format(
- "new Wicket.WUPB('%s', '%s', '%s', '%s', '%s', '%s');",
- getCallbackForm().getMarkupId(), statusDiv.getMarkupId(), barDiv.getMarkupId(), url, uploadFieldId,
- status);
+ Form<?> form = getCallbackForm();
+
+ formatter.format(getVarName() + " = new Wicket.WUPB(%s, '%s', '%s', '%s', %s, '%s', %s);",
+ form != null ? "'" + form.getMarkupId() + "'" : "null", statusDiv.getMarkupId(), barDiv.getMarkupId(), url, uploadFieldId,
+ status, getOnProgressUpdatedCallBack());
response.render(OnDomReadyHeaderItem.forScript(builder.toString()));
}
+ /**
+ * Allows to pass a JavaScript function that is called when progress in updated.
+ *
+ * @return A JavaScript function.
+ */
+ protected String getOnProgressUpdatedCallBack()
+ {
+ return "function(percent) {}";
+ }
+
+ private String getVarName() {
+ return "window.upb_" + barDiv.getMarkupId();
+ }
+
+
+ public void start(IPartialPageRequestHandler handler)
+ {
+ handler.appendJavaScript(getVarName() + ".start();");
+ }
+
/**
* Form on where will be installed the JavaScript callback to present the progress bar.
*
* @return form
*/
- private Form<?> getCallbackForm()
- {
- return form.getRootForm();
+ private Form<?> getCallbackForm() {
+ if (form != null) {
+ return form.getRootForm();
+ }
+ return null;
}
}
diff --git a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/form/upload/progressbar.js b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/form/upload/progressbar.js
index 6cd0ad930b..f540347786 100644
--- a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/form/upload/progressbar.js
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/form/upload/progressbar.js
@@ -25,16 +25,19 @@
Wicket.WUPB = Wicket.Class.create();
Wicket.WUPB.prototype = {
- initialize : function(formid, statusid, barid, url, fileid, initialStatus) {
+ initialize : function(formid, statusid, barid, url, fileid, initialStatus, onProgressUpdated) {
this.statusid = statusid;
this.barid = barid;
this.url = url;
this.fileid = fileid;
this.initialStatus = initialStatus;
+ this.onProgressUpdated = onProgressUpdated;
- var formElement = Wicket.$(formid);
- this.originalCallback = formElement.onsubmit;
- formElement.onsubmit = Wicket.bind(this.submitCallback, this);
+ if (formid) {
+ var formElement = Wicket.$(formid);
+ this.originalCallback = formElement.onsubmit;
+ formElement.onsubmit = Wicket.bind(this.submitCallback, this);
+ }
},
submitCallback : function() {
@@ -55,8 +58,14 @@
if (displayprogress) {
this.setPercent(0);
this.setStatus(this.initialStatus);
- Wicket.$(this.statusid).removeAttribute('hidden');
- Wicket.$(this.barid).removeAttribute('hidden');
+ var $statusId = Wicket.$(this.statusid);
+ if ($statusId != null) {
+ Wicket.DOM.show($statusId);
+ }
+ var $barid = Wicket.$(this.barid);
+ if ($barid != null) {
+ Wicket.DOM.show($barid);
+ }
this.scheduleUpdate();
}
},
@@ -64,15 +73,24 @@
setStatus : function(status) {
var label = document.createElement("label");
label.innerHTML = status;
- var oldLabel = Wicket.$(this.statusid).firstChild;
- if( oldLabel != null){
- Wicket.$(this.statusid).removeChild(oldLabel);
+ var $statusId = Wicket.$(this.statusid);
+ if ($statusId != null) {
+ var oldLabel = $statusId.firstChild;
+ if (oldLabel != null){
+ $statusId.removeChild(oldLabel);
+ }
+ $statusId.appendChild(label);
}
- Wicket.$(this.statusid).appendChild(label);
},
setPercent : function(progressPercent) {
- Wicket.$(this.barid).firstChild.firstChild.style.width = progressPercent + '%';
+ var barId = Wicket.$(this.barid);
+ if (barId != null && barId.firstChild != null && barId.firstChild.firstChild != null) {
+ barId.firstChild.firstChild.style.width = progressPercent + '%';
+ }
+ if (this.onProgressUpdated) {
+ this.onProgressUpdated(progressPercent);
+ }
},
scheduleUpdate : function(){
@@ -117,8 +135,14 @@
this.iframe = null;
if (progressPercent === '100') {
- Wicket.$(this.statusid).setAttribute('hidden', '');
- Wicket.$(this.barid).setAttribute('hidden', '');
+ var $statusId = Wicket.$(this.statusid);
+ if ($statusId != null) {
+ Wicket.DOM.hide($statusId);
+ }
+ var $barid = Wicket.$(this.barid);
+ if ($barid != null) {
+ Wicket.DOM.hide($barid);
+ }
} else {
this.scheduleUpdate();
}