You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by mg...@apache.org on 2021/04/20 09:19:04 UTC

[wicket] branch wicket-8.x updated: [WICKET-6876] add an AJAX behavior to transfer client side files information to server side (when a file/files is/are selected for upload at client side).

This is an automated email from the ASF dual-hosted git repository.

mgrigorov pushed a commit to branch wicket-8.x
in repository https://gitbox.apache.org/repos/asf/wicket.git


The following commit(s) were added to refs/heads/wicket-8.x by this push:
     new 47ab2fe  [WICKET-6876] add an AJAX behavior to transfer client side files information to server side (when a file/files is/are selected for upload at client side).
47ab2fe is described below

commit 47ab2fe7f8ffa66d4cb3340fb7d5ed72df473d2e
Author: reiern70 <re...@gmail.com>
AuthorDate: Thu Apr 8 08:09:02 2021 +0300

    [WICKET-6876] add an AJAX behavior to transfer client side files information to server side (when a file/files is/are selected for upload at client side).
    
    (cherry picked from commit b3d911ee33cea32a99fed285fc46bb6a11be5ea9)
---
 .../markup/html/form/upload/FileDescription.java   |  76 ++++
 .../html/form/upload/FilesSelectedBehavior.java    | 130 +++++++
 .../html/form/upload/FilesSelectedBehavior.js      |  78 ++++
 ...eUploadPage$MultipleFileUploadsSamplePanel.html |  37 ++
 ...FileUploadPage$SingleFileUploadSamplePanel.html |  25 ++
 .../examples/ajax/builtin/FileUploadPage.html      |  20 +-
 .../examples/ajax/builtin/FileUploadPage.java      | 393 ++++++++++++++++-----
 7 files changed, 664 insertions(+), 95 deletions(-)

diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/FileDescription.java b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/FileDescription.java
new file mode 100644
index 0000000..5e44cb7
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/FileDescription.java
@@ -0,0 +1,76 @@
+/*
+ * 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;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.Objects;
+import com.github.openjson.JSONObject;
+
+/**
+ * Description of file properties as in browser client side.
+ */
+public class FileDescription implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private final String fileName;
+    private final long fileSize;
+    private final Date lastModified;
+    private final String mimeType;
+
+   public FileDescription(JSONObject jsonObject) {
+        this(jsonObject.getString("fileName"), jsonObject.getLong("fileSize"),
+                jsonObject.getLong("lastModified"), jsonObject.getString("mimeType"));
+    }
+
+    public FileDescription(String fileName, long fileSize, long lastModified, String mimeType) {
+        this.fileName = fileName;
+        this.fileSize = fileSize;
+        this.lastModified = new Date(lastModified);
+        this.mimeType = mimeType;
+    }
+
+    public String getFileName() {
+        return fileName;
+    }
+
+    public long getFileSize() {
+        return fileSize;
+    }
+
+    public Date getLastModified() {
+        return lastModified;
+    }
+
+    public String getMimeType() {
+        return mimeType;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        FileDescription that = (FileDescription) o;
+        return fileName.equals(that.fileName);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(fileName);
+    }
+}
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/FilesSelectedBehavior.java b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/FilesSelectedBehavior.java
new file mode 100644
index 0000000..5328abd
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/FilesSelectedBehavior.java
@@ -0,0 +1,130 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.wicket.Component;
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
+import org.apache.wicket.ajax.attributes.IAjaxCallListener;
+import org.apache.wicket.ajax.form.OnChangeAjaxBehavior;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.JavaScriptHeaderItem;
+import org.apache.wicket.request.IRequestParameters;
+import org.apache.wicket.request.Request;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.resource.JavaScriptResourceReference;
+import org.apache.wicket.request.resource.ResourceReference;
+import org.apache.wicket.util.lang.Args;
+import org.apache.wicket.util.string.StringValue;
+import org.danekja.java.util.function.serializable.SerializableBiConsumer;
+import com.github.openjson.JSONArray;
+import com.github.openjson.JSONObject;
+
+/**
+ * {@link org.apache.wicket.ajax.form.OnChangeAjaxBehavior} that streams back to server properties
+ * of the selected file(s) (at client side), before uploading it (them).
+ *
+ * @author Ernesto Reinaldo Barreiro (reiern70@gmail.com).
+ */
+public abstract class FilesSelectedBehavior extends OnChangeAjaxBehavior {
+
+    private static final long serialVersionUID = 1L;
+
+    private static final ResourceReference JS = new JavaScriptResourceReference(FilesSelectedBehavior.class, "FilesSelectedBehavior.js");
+
+    @Override
+    protected void onBind() {
+        super.onBind();
+        Component component = getComponent();
+        if (!(component instanceof FileUploadField)) {
+            throw new WicketRuntimeException("Behavior " + getClass().getName()
+                    + " can only be added to an instance of a FileUploadField");
+        }
+    }
+
+    @Override
+    protected void onUpdate(AjaxRequestTarget target) {
+        Request request = RequestCycle.get().getRequest();
+        List<FileDescription> fileDescriptions = new ArrayList<>();
+        IRequestParameters parameters = request.getRequestParameters();
+        // data is streamed as JSON.
+        StringValue fileInfos = parameters.getParameterValue("fileInfos");
+        JSONArray jsonArray = new JSONArray(fileInfos.toString());
+        for (int i = 0; i < jsonArray.length(); i++)
+        {
+            fileDescriptions.add(new FileDescription((JSONObject)jsonArray.get(i)));
+        }
+        onSelected(target, fileDescriptions);
+    }
+
+
+    /**
+     * Called when a file, at client side is selected.
+     *
+     * @param target           The {@link org.apache.wicket.ajax.AjaxRequestTarget}
+     * @param fileDescriptions A list of FileDescription
+     */
+    protected abstract void onSelected(AjaxRequestTarget target, List<FileDescription> fileDescriptions);
+
+    @Override
+    protected void updateAjaxAttributes(AjaxRequestAttributes attributes)
+    {
+        super.updateAjaxAttributes(attributes);
+        attributes.getAjaxCallListeners().add(new IAjaxCallListener()
+        {
+            @Override
+            public CharSequence getPrecondition(Component component)
+            {
+                return "return Wicket.FilesSelected.precondition(this);";
+            }
+        });
+        attributes.getDynamicExtraParameters().add("return Wicket.FilesSelected.collectFilesDetails('" + getComponent().getMarkupId() + "');");
+    }
+
+    @Override
+    public void renderHead(Component component, IHeaderResponse response)
+    {
+        super.renderHead(component, response);
+        response.render(JavaScriptHeaderItem.forReference(JS));
+    }
+
+    /**
+     * Creates an {@link FilesSelectedBehavior} based on lambda expressions
+     *
+     * @param select {@link SerializableBiConsumer}
+     *
+     * @return the {@link FilesSelectedBehavior} behavior
+     */
+    public static FilesSelectedBehavior onSelected(
+            SerializableBiConsumer<AjaxRequestTarget, List<FileDescription>> select)
+    {
+        Args.notNull(select, "select");
+
+        return new FilesSelectedBehavior()
+        {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected void onSelected(AjaxRequestTarget target, List<FileDescription> fileDescriptions) {
+                select.accept(target, fileDescriptions);
+            }
+        };
+    }
+}
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/FilesSelectedBehavior.js b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/FilesSelectedBehavior.js
new file mode 100644
index 0000000..df89e8b
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/FilesSelectedBehavior.js
@@ -0,0 +1,78 @@
+/*
+ * 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.FilesSelected) === 'object') {
+        return;
+    }
+
+    /**
+     * Contains JS code for FilesSelectedBehavior.
+     */
+    Wicket.FilesSelected = {
+
+        // the id of temporary object holding files.
+        varId: function (componentId) {
+            return componentId + "_files";
+        },
+
+        /**
+         * Precondition to trigger (or not) server round-trip.
+         *
+         * @param inputField the file input field
+         * @returns {boolean} true if some files (file) were (was) selected,
+         */
+        precondition : function (inputField) {
+            if (inputField.files && inputField.files.length > 0) {
+                var id = this.varId(inputField.id);
+                Wicket.FilesSelected[id] = inputField.files;
+                return true;
+            }
+            return false;
+        },
+
+        /**
+         * Collects selected files details
+         *
+         * @param componentId The id of file upload input field.
+         *
+         * @returns array with file infos.
+         */
+        collectFilesDetails: function(componentId) {
+            var id = this.varId(componentId);
+            var sources = Wicket.FilesSelected[id];
+            var files = [];
+            for (var i = 0; i < sources.length; i++) {
+                var file = sources[i];
+                var info = {
+                    'fileName': file.name,
+                    'fileSize': file.size,
+                    'lastModified': file.lastModified,
+                    'mimeType': file.type
+                }
+                files.push(info);
+            }
+            // clean temporary holder object
+            delete(Wicket.FilesSelected[id]);
+            // return information about selected files.
+            return {'fileInfos': JSON.stringify(files)};
+        }
+    };
+})();
\ No newline at end of file
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/FileUploadPage$MultipleFileUploadsSamplePanel.html b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/FileUploadPage$MultipleFileUploadsSamplePanel.html
new file mode 100644
index 0000000..201fda4
--- /dev/null
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/FileUploadPage$MultipleFileUploadsSamplePanel.html
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
+<head>
+    <wicket:head>
+        <script type="text/css">
+            table.files {
+                width: 1000px;
+                display: table;
+            }
+            table.files tbody {
+                width: 100%;
+            }
+            table.files td {
+                width: 25%;
+            }
+        </script>
+    </wicket:head>
+</head>
+<body>
+<wicket:panel>
+    <h2>Uploading a multiple files</h2>
+
+    <div wicket:id="feedback"></div>
+
+    <form wicket:id="form">
+        Text field: <input wicket:id="text" type="text"/><br/>
+        File field (can select multiple files): <input wicket:id="file" type="file" multiple="multiple"/> (<span wicket:id="max"></span> max)<br/><br/>
+        <br/>
+        <table wicket:id="selectedFileInfo" class="files">
+        </table>
+        <br/>
+        <div wicket:id="progress"></div>
+        <input wicket:id="submit" type="submit" value="Regular Submit"/> <input wicket:id="ajaxSubmit" type="button" value="Ajax Submit"/>
+    </form>
+</wicket:panel>
+</body>
+</html>
\ No newline at end of file
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/FileUploadPage$SingleFileUploadSamplePanel.html b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/FileUploadPage$SingleFileUploadSamplePanel.html
new file mode 100644
index 0000000..1addf8c
--- /dev/null
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/FileUploadPage$SingleFileUploadSamplePanel.html
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
+<body>
+<wicket:panel>
+
+    <h2>Uploading a single file</h2>
+
+    <div wicket:id="feedback"></div>
+
+    <form wicket:id="form">
+        Text field: <input wicket:id="text" type="text"/><br/>
+        File field: <input wicket:id="file" type="file"/> (<span wicket:id="max"></span> max)<br/><br/>
+        <br/>
+        <div wicket:id="selectedFileInfo"></div>
+        <br/>
+        <div wicket:id="progress"></div>
+        <input wicket:id="submit" type="submit" value="Regular Submit"/> <input wicket:id="ajaxSubmit" type="button" value="Ajax Submit"/>
+    </form>
+
+    <div wicket:id="drop" class="drop-zone">
+        Drop file here.
+    </div>
+</wicket:panel>
+</body>
+</html>
\ No newline at end of file
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/FileUploadPage.html b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/FileUploadPage.html
index 18d2bfb..46f3612 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/FileUploadPage.html
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/FileUploadPage.html
@@ -18,21 +18,13 @@
 <body>
 <wicket:extend xmlns:wicket="http://wicket.apache.org">
 
-Demonstrates Wicket's ability to transparently handle multipart forms via AJAX.<br/><br/>The only difference between this example and other non-AJAX upload examples is the option to trigger the form submit via an AjaxButton, everything else is handled transparently by Wicket.<br/><br/>
+    Demonstrates Wicket's ability to transparently handle multipart forms via AJAX.<br/><br/>The only difference
+    between this example and other non-AJAX upload examples is the option to trigger the form submit via an AjaxButton,
+    everything else is handled transparently by Wicket.<br/><br/>
 
-
-<div wicket:id="feedback"></div>
-
-<form wicket:id="form">
-	Text field: <input wicket:id="text" type="text"/><br/>
-	File field: <input wicket:id="file" type="file"/> (<span wicket:id="max"></span> max)<br/><br/>
-	<div wicket:id="progress"></div>
-	<input type="submit" value="Regular Submit"/> <input wicket:id="ajaxSubmit" type="button" value="Ajax Submit"/>
-</form>
-
-<div wicket:id="drop" class="drop-zone">
-	Drop file here.
-</div>
+    <div wicket:id="singleFileUpload"></div>
+    <br/>
+    <div wicket:id="multipleFileUpload"></div>
 
 </wicket:extend>
 </body>
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/FileUploadPage.java b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/FileUploadPage.java
index c7f25fb..a84b45b 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/FileUploadPage.java
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/FileUploadPage.java
@@ -16,21 +16,33 @@
  */
 package org.apache.wicket.examples.ajax.builtin;
 
+import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 
+import org.apache.commons.collections4.iterators.EmptyIterator;
 import org.apache.commons.fileupload.FileUploadException;
 import org.apache.wicket.Component;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.markup.html.form.AjaxButton;
 import org.apache.wicket.extensions.ajax.AjaxFileDropBehavior;
 import org.apache.wicket.extensions.ajax.markup.html.form.upload.UploadProgressBar;
+import org.apache.wicket.extensions.ajax.markup.html.repeater.data.table.AjaxFallbackDefaultDataTable;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
+import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
 import org.apache.wicket.markup.html.WebMarkupContainer;
 import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.Button;
 import org.apache.wicket.markup.html.form.Form;
 import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.markup.html.form.upload.FileDescription;
 import org.apache.wicket.markup.html.form.upload.FileUpload;
 import org.apache.wicket.markup.html.form.upload.FileUploadField;
+import org.apache.wicket.markup.html.form.upload.FilesSelectedBehavior;
 import org.apache.wicket.markup.html.panel.FeedbackPanel;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.Model;
 import org.apache.wicket.util.lang.Bytes;
 import org.apache.wicket.validation.validator.StringValidator;
@@ -44,115 +56,334 @@ public class FileUploadPage extends BasePage
 {
 	private static final long serialVersionUID = 1L;
 
-	private final FileUploadField file;
-	private final TextField<String> text;
+	private static class SingleFileUploadSamplePanel extends Panel {
 
-	/**
-	 * Constructor
-	 */
-	public FileUploadPage()
-	{
+		private final FileUploadField file;
+		private final TextField<String> text;
+		private final Label selectedFileInfo;
+		private final AjaxButton ajaxSubmit;
+		private final Button submit;
+		private String fileInfo;
 
-		// create a feedback panel
-		final Component feedback = new FeedbackPanel("feedback").setOutputMarkupId(true);
-		add(feedback);
+		public SingleFileUploadSamplePanel(String id) {
+			super(id);
 
-		// create the form
-		final Form<?> form = new Form<Void>("form")
-		{
-			private static final long serialVersionUID = 1L;
+			// create a feedback panel
+			final Component feedback = new FeedbackPanel("feedback").setOutputMarkupId(true);
+			add(feedback);
 
-			@Override
-			protected void onSubmit()
+			// create the form
+			final Form<?> form = new Form<Void>("form")
 			{
-				// display uploaded info
-				info("Text: " + text.getModelObject());
-				FileUpload upload = file.getFileUpload();
-				if (upload == null)
+				private static final long serialVersionUID = 1L;
+
+				@Override
+				protected void onSubmit()
 				{
-					info("No file uploaded");
+					// display uploaded info
+					info("Text: " + text.getModelObject());
+					FileUpload upload = file.getFileUpload();
+					if (upload == null)
+					{
+						info("No file uploaded");
+					}
+					else
+					{
+						info("File-Name: " + upload.getClientFileName() + " File-Size: " +
+								Bytes.bytes(upload.getSize()).toString());
+					}
 				}
-				else
+			};
+			form.setMaxSize(Bytes.megabytes(1));
+			add(form);
+
+			// create a textfield to demo non-file content
+			form.add(text = new TextField<>("text", Model.of()));
+			text.add(StringValidator.minimumLength(2));
+
+			// create the file upload field
+			form.add(file = new FileUploadField("file"));
+
+			add(selectedFileInfo = new Label("selectedFileInfo", (IModel<String>) () -> fileInfo) {
+				@Override
+				protected void onAfterRender() {
+					super.onAfterRender();
+					fileInfo = null;
+				}
+			});
+			selectedFileInfo.setOutputMarkupId(true);
+			form.add(selectedFileInfo);
+
+			form.add(new Label("max", form::getMaxSize));
+
+			form.add(new UploadProgressBar("progress", form, file));
+
+			// create a submit button
+			form.add(submit = new Button("submit"));
+			submit.setOutputMarkupId(true);
+
+			// create the ajax button used to submit the form
+			form.add(ajaxSubmit = new AjaxButton("ajaxSubmit")
+			{
+				private static final long serialVersionUID = 1L;
+
+				/**
+				 * Need to trigger submit to initiate progressbar.
+				 */
+				@Override
+				protected boolean shouldTriggerJavaScriptSubmitEvent()
 				{
-					info("File-Name: " + upload.getClientFileName() + " File-Size: " +
-						Bytes.bytes(upload.getSize()).toString());
+					return true;
 				}
-			}
-		};
-		form.setMaxSize(Bytes.megabytes(1));
-		add(form);
 
-		// create a textfield to demo non-file content
-		form.add(text = new TextField<>("text", Model.of()));
-		text.add(StringValidator.minimumLength(2));
+				@Override
+				protected void onSubmit(AjaxRequestTarget target)
+				{
+					info("This request was processed using AJAX");
+
+					// ajax-update the feedback panel
+					target.add(feedback);
+				}
+
+				@Override
+				protected void onError(AjaxRequestTarget target)
+				{
+					// update feedback to display errors
+					target.add(feedback);
+				}
+
+			});
+			ajaxSubmit.setOutputMarkupId(true);
 
-		// create the file upload field
-		form.add(file = new FileUploadField("file"));
+			file.add(FilesSelectedBehavior.onSelected(
+					(AjaxRequestTarget target, List<FileDescription> fileDescriptions)->
+					{
+						FileDescription fileDescription = fileDescriptions.get(0);
+						Bytes bytes = Bytes.bytes(fileDescription.getFileSize());
+						fileInfo = "File " + fileDescription.getFileName() +
+								" (with size " + bytes + ") was selected at client side. "
+								+ "File was last modified at: " + fileDescription.getLastModified()
+								+ " and is of type " + fileDescription.getMimeType() +
+								". It has not been uploaded yet. ";
+						if (bytes.greaterThan(form.getMaxSize()))
+						{
+							fileInfo += " File exceeds max allowed size.";
+							// disable buttons as file is not valid
+							submit.setEnabled(false);
+							ajaxSubmit.setEnabled(false);
+						}
+						else
+						{
+							fileInfo += " You can click on buttons bellow in order to upload it.";
+							// enable buttons as file is valid
+							submit.setEnabled(true);
+							ajaxSubmit.setEnabled(true);
+						}
+						target.add(selectedFileInfo, submit, ajaxSubmit);
+					}));
+			WebMarkupContainer drop = new WebMarkupContainer("drop");
+			drop.add(new AjaxFileDropBehavior() {
+				protected void onFileUpload(AjaxRequestTarget target, List<FileUpload> files) {
 
-		form.add(new Label("max", form::getMaxSize));
+					// display uploaded info
+					if (files == null || files.isEmpty())
+					{
+						info("No file uploaded");
+					}
+					else
+					{
+						for (FileUpload file : files) {
+							info("File-Name: " + file.getClientFileName() + " File-Size: " +
+									Bytes.bytes(file.getSize()).toString());
+						}
+					}
 
-		form.add(new UploadProgressBar("progress", form, file));
+					target.add(feedback);
+				}
+
+				@Override
+				protected void onError(AjaxRequestTarget target, FileUploadException fux)
+				{
+					info(fux.getMessage());
+
+					target.add(feedback);
+				}
+			});
+			add(drop);
+		}
+	}
 
-		// create the ajax button used to submit the form
-		form.add(new AjaxButton("ajaxSubmit")
-		{
-			private static final long serialVersionUID = 1L;
+	private static class MultipleFileUploadsSamplePanel extends Panel {
+
+		private static class DataProvider extends SortableDataProvider<FileDescription, String> {
+
+			private List<FileDescription> fileDescriptions;
+
+			public void setFileDescriptions(List<FileDescription> fileDescriptions) {
+				this.fileDescriptions = new ArrayList<>(fileDescriptions);
+			}
 
-			/**
-			 * Need to trigger submit to initiate progressbar. 
-			 */
 			@Override
-			protected boolean shouldTriggerJavaScriptSubmitEvent()
-			{
-				return true;
+			public Iterator<? extends FileDescription> iterator(long first, long count) {
+				if (this.fileDescriptions == null) {
+					return EmptyIterator.emptyIterator();
+				}
+				return fileDescriptions.listIterator();
 			}
-			
+
 			@Override
-			protected void onSubmit(AjaxRequestTarget target)
-			{
-				info("This request was processed using AJAX");
+			public long size() {
+				if (this.fileDescriptions == null) {
+					return 0L;
+				}
+				return this.fileDescriptions.size();
+			}
 
-				// ajax-update the feedback panel
-				target.add(feedback);
+			@Override
+			public IModel<FileDescription> model(FileDescription object) {
+				return Model.of(object);
 			}
 
 			@Override
-			protected void onError(AjaxRequestTarget target)
-			{
-				// update feedback to display errors
-				target.add(feedback);
+			public void detach() {
+				super.detach();
+				this.fileDescriptions = null;
 			}
+		}
+
+		private final FileUploadField file;
+		private final TextField<String> text;
+		private DataProvider dataProvider;
+		private AjaxFallbackDefaultDataTable<FileDescription, String> selectedFileInfo;
+		private final AjaxButton ajaxSubmit;
+		private final Button submit;
 
-		});
-		
-		WebMarkupContainer drop = new WebMarkupContainer("drop");
-		drop.add(new AjaxFileDropBehavior() {
-			protected void onFileUpload(AjaxRequestTarget target, List<FileUpload> files) {
-			    
-				// display uploaded info
-				if (files == null || files.isEmpty())
+		public MultipleFileUploadsSamplePanel(String id) {
+			super(id);
+
+			// create a feedback panel
+			final Component feedback = new FeedbackPanel("feedback").setOutputMarkupId(true);
+			add(feedback);
+
+			// create the form
+			final Form<?> form = new Form<Void>("form")
+			{
+				private static final long serialVersionUID = 1L;
+
+				@Override
+				protected void onSubmit()
 				{
-					info("No file uploaded");
+					// display uploaded info
+					info("Text: " + text.getModelObject());
+					FileUpload upload = file.getFileUpload();
+					if (upload == null)
+					{
+						info("No file uploaded");
+					}
+					else
+					{
+						info("File-Name: " + upload.getClientFileName() + " File-Size: " +
+								Bytes.bytes(upload.getSize()).toString());
+					}
 				}
-				else
+			};
+			form.setMaxSize(Bytes.megabytes(1));
+			add(form);
+
+			List<IColumn<FileDescription, String>> columns = new ArrayList<>();
+			columns.add(new PropertyColumn<>(Model.of("File Name"), "fileName"));
+			columns.add(new PropertyColumn<>(Model.of("Size"), "fileSize"));
+			columns.add(new PropertyColumn<>(Model.of("Last Modified"), "lastModified"));
+			columns.add(new PropertyColumn<>(Model.of("MIME Type"), "mimeType"));
+			selectedFileInfo = new AjaxFallbackDefaultDataTable<>("selectedFileInfo", columns, dataProvider = new DataProvider(), 100) {
+				@Override
+				protected void onConfigure() {
+					super.onConfigure();
+					setVisible(dataProvider.size() > 0);
+				}
+			};
+			form.add(selectedFileInfo);
+			selectedFileInfo.setOutputMarkupPlaceholderTag(true);
+
+			// create a textfield to demo non-file content
+			form.add(text = new TextField<>("text", Model.of()));
+			text.add(StringValidator.minimumLength(2));
+
+			// create the file upload field
+			form.add(file = new FileUploadField("file"));
+
+			form.add(new Label("max", form::getMaxSize));
+
+			form.add(new UploadProgressBar("progress", form, file));
+
+			// create a submit button
+			form.add(submit = new Button("submit"));
+			submit.setOutputMarkupId(true);
+
+			// create the ajax button used to submit the form
+			form.add(ajaxSubmit = new AjaxButton("ajaxSubmit")
+			{
+				private static final long serialVersionUID = 1L;
+
+				/**
+				 * Need to trigger submit to initiate progressbar.
+				 */
+				@Override
+				protected boolean shouldTriggerJavaScriptSubmitEvent()
 				{
-				    for (FileUpload file : files) {
-				    	info("File-Name: " + file.getClientFileName() + " File-Size: " +
-				    		Bytes.bytes(file.getSize()).toString());
-				    }
+					return true;
 				}
-				
-				target.add(feedback);
-			}
-			
-			@Override
-			protected void onError(AjaxRequestTarget target, FileUploadException fux)
+
+				@Override
+				protected void onSubmit(AjaxRequestTarget target)
+				{
+					info("This request was processed using AJAX");
+
+					// ajax-update the feedback panel
+					target.add(feedback);
+				}
+
+				@Override
+				protected void onError(AjaxRequestTarget target)
+				{
+					// update feedback to display errors
+					target.add(feedback);
+				}
+
+			});
+			ajaxSubmit.setOutputMarkupId(true);
+			file.add(FilesSelectedBehavior.onSelected(
+					(AjaxRequestTarget target, List<FileDescription> fileDescriptions) ->
 			{
-				info(fux.getMessage());
-				
-				target.add(feedback);				
-			}
-		});
-		add(drop);
+				dataProvider.setFileDescriptions(fileDescriptions);
+				Bytes bytes = Bytes.bytes(fileDescriptions.stream().mapToLong(FileDescription::getFileSize).sum());
+				if (bytes.greaterThan(form.getMaxSize()))
+				{
+					form.error("Total file size exceeds max allowed size.");
+					// disable buttons as file is not valid
+					submit.setEnabled(false);
+					ajaxSubmit.setEnabled(false);
+				}
+				else
+				{
+					form.info("You can click on buttons bellow in order to upload selected files.");
+					// enable buttons as file is valid
+					submit.setEnabled(true);
+					ajaxSubmit.setEnabled(true);
+				}
+				target.add(selectedFileInfo, submit, ajaxSubmit, feedback);
+			}));
+		}
+	}
+	/**
+	 * Constructor
+	 */
+	public FileUploadPage()
+	{
+		// sample of a single uploaded file.
+		add(new SingleFileUploadSamplePanel("singleFileUpload"));
+		add(new MultipleFileUploadsSamplePanel("multipleFileUpload"));
+
+
 	}
 }