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

reiern70 pushed a commit to branch upload-to-resource
in repository https://gitbox.apache.org/repos/asf/wicket.git

commit 3842cb78f83ea75012ac440d7ebbcecc1b040a72
Author: reiern70 <[email protected]>
AuthorDate: Thu Oct 15 11:08:00 2026 -0500

    Add a drop zone and a file list to uploads to a resource
    
    GitHub issue #1669
    
    FileUploadToResourceField uploaded the files of its input as the
    browser's dialog left them: each choice replaced the one before, files
    could not be dropped, and a file could not be taken out again before the
    upload.
    
    Two behaviors add that:
    
    - FileDropZoneBehavior makes a component a zone files are dropped on.
      The input receives the dropped files it accepts by its accept
      attribute, all of them if it is multiple, else the first; folders are
      left out. It then fires its input and change events, as for files
      chosen in the dialog. While files are dragged over the zone, it has the
      CSS class wicket-upload-dragover.
    
    - FileListBehavior, on a <ul> or <ol>, lists the files chosen for the
      next upload, each with a button removing it, whose text is the resource
      uploadToResourceRemove. With a list the field collects files: if its
      input is multiple, files chosen or dropped are added to the listed ones
      instead of replacing them. The files an upload sent leave the list once
      it succeeded, while files chosen meanwhile stay; during an upload the
      remove buttons are disabled. A file name is written as text, never as
      markup.
    
    The script keeps the chosen files and writes them back into the input
    through a DataTransfer, so the upload, the progress bar and other
    listeners of the input see all of them. It hears of a choice in the
    capture phase, before the input's other change listeners run.
    
    A field without these behaviors works as before: a choice replaces the
    files of the input, and the input keeps them after the upload.
    
    The script's guard against being evaluated twice tested for an object,
    but Wicket.FileUploadToResourceField is a function, so a second
    evaluation replaced it. It tests for a function now, as the registry of
    file lists lives in the script.
    
    The upload-to-resource example shows a drop zone and a file list. Its
    styles never applied: they were in a <head> the page ignores, since it
    extends WicketExamplePage, and inline, which the Content Security Policy
    of the examples blocks. They are in UploadToResourcePage.css now, under
    a class name that does not clash with the drop-zone of the examples'
    style.css. Its guard against leaving the page during an upload used
    jQuery, which the plain JavaScript engine does not load; it uses
    Wicket.Event now.
---
 .../upload/resource/FileDropZoneBehaviorTest.java  |  70 +++++
 .../form/upload/resource/FileListBehaviorTest.java |  81 ++++++
 .../form/upload/resource/FileDropZoneBehavior.java |  84 ++++++
 .../form/upload/resource/FileListBehavior.java     | 107 ++++++++
 .../upload/resource/FileUploadToResourceField.java |   5 +-
 .../upload/resource/FileUploadToResourceField.js   | 293 ++++++++++++++++++++-
 .../resource/FileUploadToResourceField.properties  |   1 +
 wicket-core/src/test/js/all.html                   |   3 +
 wicket-core/src/test/js/upload-to-resource.js      | 181 ++++++++++++-
 .../examples/upload/UploadToResourcePage.css       |  22 ++
 .../examples/upload/UploadToResourcePage.html      |  18 +-
 .../examples/upload/UploadToResourcePage.java      |  22 +-
 .../upload/UploadToResourcePage.properties         |   2 +-
 13 files changed, 862 insertions(+), 27 deletions(-)

diff --git 
a/wicket-core-tests/src/test/java/org/apache/wicket/markup/html/form/upload/resource/FileDropZoneBehaviorTest.java
 
b/wicket-core-tests/src/test/java/org/apache/wicket/markup/html/form/upload/resource/FileDropZoneBehaviorTest.java
new file mode 100644
index 0000000000..1a90e6795d
--- /dev/null
+++ 
b/wicket-core-tests/src/test/java/org/apache/wicket/markup/html/form/upload/resource/FileDropZoneBehaviorTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.regex.Pattern;
+import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.markup.IMarkupResourceStreamProvider;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.WebPage;
+import 
org.apache.wicket.markup.html.form.upload.resource.FileUploadToResourceFieldTest.TestField;
+import 
org.apache.wicket.markup.html.form.upload.resource.FileUploadToResourceFieldTest.TestFileUploadResourceReference;
+import org.apache.wicket.util.file.File;
+import org.apache.wicket.util.resource.IResourceStream;
+import org.apache.wicket.util.resource.StringResourceStream;
+import org.apache.wicket.util.tester.WicketTestCase;
+import org.junit.jupiter.api.Test;
+
+class FileDropZoneBehaviorTest extends WicketTestCase
+{
+       @Test
+       void makesTheComponentADropZoneOfTheField()
+       {
+               DropZonePage page = tester.startPage(new DropZonePage());
+
+               
tester.assertContains(Pattern.quote("Wicket.FileUploadToResourceField.addDropZone(\""
 +
+                       page.zone.getMarkupId() + "\", \"" + 
page.field.getMarkupId() + "\", \"" +
+                       FileDropZoneBehavior.DRAG_OVER_CSS_CLASS + "\");"));
+       }
+
+       static class DropZonePage extends WebPage implements 
IMarkupResourceStreamProvider
+       {
+               private static final long serialVersionUID = 1L;
+
+               final TestField field;
+
+               final WebMarkupContainer zone;
+
+               DropZonePage()
+               {
+                       zone = new WebMarkupContainer("zone");
+                       add(zone);
+                       field = new TestField("upload", new 
TestFileUploadResourceReference(
+                               new FolderUploadsFileManager(new 
File(System.getProperty("java.io.tmpdir")))));
+                       zone.add(field);
+                       zone.add(new FileDropZoneBehavior(field));
+               }
+
+               @Override
+               public IResourceStream getMarkupResourceStream(MarkupContainer 
container,
+                       Class<?> containerClass)
+               {
+                       return new StringResourceStream("<html><body><div 
wicket:id=\"zone\">" +
+                               "<input type=\"file\" wicket:id=\"upload\" 
multiple/></div></body></html>");
+               }
+       }
+}
diff --git 
a/wicket-core-tests/src/test/java/org/apache/wicket/markup/html/form/upload/resource/FileListBehaviorTest.java
 
b/wicket-core-tests/src/test/java/org/apache/wicket/markup/html/form/upload/resource/FileListBehaviorTest.java
new file mode 100644
index 0000000000..9b230d443a
--- /dev/null
+++ 
b/wicket-core-tests/src/test/java/org/apache/wicket/markup/html/form/upload/resource/FileListBehaviorTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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 static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.regex.Pattern;
+import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.markup.IMarkupResourceStreamProvider;
+import org.apache.wicket.markup.MarkupException;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.WebPage;
+import 
org.apache.wicket.markup.html.form.upload.resource.FileUploadToResourceFieldTest.TestField;
+import 
org.apache.wicket.markup.html.form.upload.resource.FileUploadToResourceFieldTest.TestFileUploadResourceReference;
+import org.apache.wicket.util.file.File;
+import org.apache.wicket.util.resource.IResourceStream;
+import org.apache.wicket.util.resource.StringResourceStream;
+import org.apache.wicket.util.tester.WicketTestCase;
+import org.junit.jupiter.api.Test;
+
+class FileListBehaviorTest extends WicketTestCase
+{
+       @Test
+       void listsTheFilesOfTheField()
+       {
+               ListPage page = tester.startPage(new ListPage("ul"));
+
+               
tester.assertContains(Pattern.quote("Wicket.FileUploadToResourceField.addList(\""
 +
+                       page.field.getMarkupId() + "\", \"" + 
page.list.getMarkupId() + "\", \"Remove\");"));
+       }
+
+       @Test
+       void canOnlyBeAttachedToAList()
+       {
+               assertThrows(MarkupException.class, () -> tester.startPage(new 
ListPage("div")));
+       }
+
+       static class ListPage extends WebPage implements 
IMarkupResourceStreamProvider
+       {
+               private static final long serialVersionUID = 1L;
+
+               private final String listTag;
+
+               final TestField field;
+
+               final WebMarkupContainer list;
+
+               ListPage(String listTag)
+               {
+                       this.listTag = listTag;
+                       field = new TestField("upload", new 
TestFileUploadResourceReference(
+                               new FolderUploadsFileManager(new 
File(System.getProperty("java.io.tmpdir")))));
+                       add(field);
+                       list = new WebMarkupContainer("list");
+                       list.add(new FileListBehavior(field));
+                       add(list);
+               }
+
+               @Override
+               public IResourceStream getMarkupResourceStream(MarkupContainer 
container,
+                       Class<?> containerClass)
+               {
+                       return new StringResourceStream("<html><body><input 
type=\"file\" wicket:id=\"upload\"/><" +
+                               listTag + " wicket:id=\"list\"></" + listTag + 
"></body></html>");
+               }
+       }
+}
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileDropZoneBehavior.java
 
b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileDropZoneBehavior.java
new file mode 100644
index 0000000000..686e79a87a
--- /dev/null
+++ 
b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileDropZoneBehavior.java
@@ -0,0 +1,84 @@
+/*
+ * 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.Component;
+import org.apache.wicket.behavior.Behavior;
+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.resource.CoreLibrariesContributor;
+import org.apache.wicket.util.lang.Args;
+import com.github.openjson.JSONObject;
+
+/**
+ * Makes a component a zone files are dropped on to choose them for the next 
upload of a
+ * {@link FileUploadToResourceField}, as if they were chosen in the browser's 
dialog of its input.
+ * The input receives the dropped files it accepts by its {@code accept} 
attribute: all of them if
+ * it is {@code multiple}, else the first. Folders are left out. The input 
then fires its
+ * {@code input} and {@code change} events, as it does for files chosen in the 
dialog.
+ * <p>
+ * With a {@link FileListBehavior}, dropped files are added to those chosen 
before. While files are
+ * dragged over the component, it has the CSS class {@value 
#DRAG_OVER_CSS_CLASS}.
+ *
+ * <pre>
+ * FileUploadToResourceField field = ...;
+ * add(new WebMarkupContainer("dropZone").add(new 
FileDropZoneBehavior(field)));
+ * </pre>
+ *
+ * @since 11.0.0
+ */
+public class FileDropZoneBehavior extends Behavior
+{
+       private static final long serialVersionUID = 1L;
+
+       /**
+        * The CSS class of the component while files are dragged over it.
+        */
+       public static final String DRAG_OVER_CSS_CLASS = 
"wicket-upload-dragover";
+
+       private final FileUploadToResourceField field;
+
+       /**
+        * @param field
+        *            the field the dropped files are chosen for
+        */
+       public FileDropZoneBehavior(FileUploadToResourceField field)
+       {
+               this.field = Args.notNull(field, "field");
+       }
+
+       @Override
+       public void bind(Component component)
+       {
+               super.bind(component);
+               component.setOutputMarkupId(true);
+       }
+
+       @Override
+       public void renderHead(Component component, IHeaderResponse response)
+       {
+               super.renderHead(component, response);
+               
CoreLibrariesContributor.contributeAjax(component.getApplication(), response);
+               
response.render(JavaScriptHeaderItem.forReference(FileUploadToResourceField.JS));
+               response.render(OnDomReadyHeaderItem.forScript(
+                       "Wicket.FileUploadToResourceField.addDropZone(" +
+                               JSONObject.quote(component.getMarkupId()) + ", 
" +
+                               JSONObject.quote(field.getMarkupId()) + ", " +
+                               JSONObject.quote(DRAG_OVER_CSS_CLASS) + ");"));
+       }
+}
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileListBehavior.java
 
b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileListBehavior.java
new file mode 100644
index 0000000000..34139e0f4a
--- /dev/null
+++ 
b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileListBehavior.java
@@ -0,0 +1,107 @@
+/*
+ * 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.Component;
+import org.apache.wicket.behavior.Behavior;
+import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.MarkupException;
+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.resource.CoreLibrariesContributor;
+import org.apache.wicket.util.lang.Args;
+import com.github.openjson.JSONObject;
+
+/**
+ * Lists the files chosen for the next upload of a {@link 
FileUploadToResourceField}, each with a
+ * button removing it. It is attached to a {@code <ul>} or an {@code <ol>}, 
whose items it replaces
+ * in the browser with one for each file:
+ *
+ * <pre>
+ * &lt;li class="wicket-upload-file"&gt;
+ *   &lt;span class="wicket-upload-file-name"&gt;report.pdf&lt;/span&gt;
+ *   &lt;button type="button" 
class="wicket-upload-file-remove"&gt;Remove&lt;/button&gt;
+ * &lt;/li&gt;
+ * </pre>
+ *
+ * The name of a file is written as text, never as markup. The text of the 
buttons is the resource
+ * {@value #REMOVE_RESOURCE_KEY} of the field.
+ * <p>
+ * With a list, the field collects the files of several choices: if its input 
is
+ * {@code multiple}, files chosen in the browser's dialog or dropped on a
+ * {@link FileDropZoneBehavior drop zone} are added to the listed ones instead 
of replacing them.
+ * Once an upload succeeded, the files it sent leave the list. While an upload 
runs, no file can be
+ * removed.
+ *
+ * <pre>
+ * FileUploadToResourceField field = ...;
+ * add(new WebMarkupContainer("files").add(new FileListBehavior(field)));
+ * </pre>
+ *
+ * @since 11.0.0
+ */
+public class FileListBehavior extends Behavior
+{
+       private static final long serialVersionUID = 1L;
+
+       /**
+        * The resource key of the text of the buttons removing a file.
+        */
+       public static final String REMOVE_RESOURCE_KEY = 
"uploadToResourceRemove";
+
+       private final FileUploadToResourceField field;
+
+       /**
+        * @param field
+        *            the field whose files are listed
+        */
+       public FileListBehavior(FileUploadToResourceField field)
+       {
+               this.field = Args.notNull(field, "field");
+       }
+
+       @Override
+       public void bind(Component component)
+       {
+               super.bind(component);
+               component.setOutputMarkupId(true);
+       }
+
+       @Override
+       public void onComponentTag(Component component, ComponentTag tag)
+       {
+               super.onComponentTag(component, tag);
+               if (!tag.getName().equalsIgnoreCase("ul") && 
!tag.getName().equalsIgnoreCase("ol"))
+               {
+                       throw new MarkupException("A FileListBehavior can only 
be attached to <ul> or <ol>, " +
+                               "not to <" + tag.getName() + "> of " + 
component);
+               }
+       }
+
+       @Override
+       public void renderHead(Component component, IHeaderResponse response)
+       {
+               super.renderHead(component, response);
+               
CoreLibrariesContributor.contributeAjax(component.getApplication(), response);
+               
response.render(JavaScriptHeaderItem.forReference(FileUploadToResourceField.JS));
+               
response.render(OnDomReadyHeaderItem.forScript("Wicket.FileUploadToResourceField.addList("
 +
+                       JSONObject.quote(field.getMarkupId()) + ", " +
+                       JSONObject.quote(component.getMarkupId()) + ", " +
+                       JSONObject.quote(field.getString(REMOVE_RESOURCE_KEY)) 
+ ");"));
+       }
+}
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
index 97d5822508..502e2c7a84 100644
--- 
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
@@ -55,6 +55,9 @@ 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.
+ * <p>
+ * A {@link FileDropZoneBehavior} makes a component a zone the files to upload 
can be dropped on,
+ * and a {@link FileListBehavior} lists the files chosen, which then add up 
over several choices.
  */
 public abstract class FileUploadToResourceField extends FileUploadField
 {
@@ -199,7 +202,7 @@ public abstract class FileUploadToResourceField extends 
FileUploadField
         }
     }
 
-    private static final JavaScriptResourceReference JS = new 
JavaScriptResourceReference(FileUploadToResourceField.class, 
"FileUploadToResourceField.js");
+    static final JavaScriptResourceReference JS = new 
JavaScriptResourceReference(FileUploadToResourceField.class, 
"FileUploadToResourceField.js");
 
     public static String UPLOAD_CANCELED = "upload.canceled";
 
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
index 31a7580d61..f45c7f152b 100644
--- 
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
@@ -19,10 +19,20 @@
 
     'use strict';
 
-    if (typeof(Wicket.FileUploadToResourceField) === 'object') {
+    if (typeof(Wicket.FileUploadToResourceField) === 'function') {
         return;
     }
 
+    // the keys of the errors, as the field knows them
+    const UPLOAD_TOO_LARGE = 'uploadTooLarge';
+    const UPLOAD_SINGLE_FILE_TOO_LARGE = 'uploadSingleFileTooLarge';
+    const UPLOAD_TOO_MANY_FILES = 'uploadTooManyFiles';
+    const UPLOAD_FAILED = 'uploadFailed';
+    const UPLOAD_CANCELED = 'upload.canceled';
+
+    // the file lists of the fields, by field id: the text of the remove 
buttons, by list id
+    const lists = {};
+
     Wicket.FileUploadToResourceField = function (settings, 
clientBeforeSendCallBack, clientSideSuccessCallBack, clientSideCancelCallBack, 
uploadErrorCallBack)
     {
         this.settings = settings;
@@ -41,14 +51,254 @@
         this.clientSideSuccessCallBack = clientSideSuccessCallBack;
         this.clientSideCancelCallBack = clientSideCancelCallBack;
         this.uploadErrorCallBack = uploadErrorCallBack;
+        // the files chosen for the next upload, which the input holds as well
+        this.files = Array.from(this.input.files);
+        const self = this;
+        // in the capture phase, so the other listeners of the input see all 
the chosen files
+        this.input.addEventListener('change', function () {
+            if (!self.dispatching) {
+                self.choose(Array.from(self.input.files));
+            }
+        }, true);
+        this.renderLists();
     };
 
-    // the keys of the errors, as the field knows them
-    const UPLOAD_TOO_LARGE = 'uploadTooLarge';
-    const UPLOAD_SINGLE_FILE_TOO_LARGE = 'uploadSingleFileTooLarge';
-    const UPLOAD_TOO_MANY_FILES = 'uploadTooManyFiles';
-    const UPLOAD_FAILED = 'uploadFailed';
-    const UPLOAD_CANCELED = 'upload.canceled';
+    /**
+     * Makes a file list of a field show the files chosen for its next upload. 
With a list, the
+     * files chosen are added to those chosen before if the input is multiple, 
and leave the list
+     * once they are uploaded.
+     *
+     * @param fieldId the id of the field
+     * @param listId the id of the list, a ul or an ol
+     * @param removeText the text of the buttons removing a file
+     */
+    Wicket.FileUploadToResourceField.addList = function (fieldId, listId, 
removeText)
+    {
+        lists[fieldId] = lists[fieldId] || {};
+        lists[fieldId][listId] = removeText;
+        const field = Wicket.Timer[fieldId];
+        if (field) {
+            field.renderLists();
+        }
+    };
+
+    /**
+     * Makes an element a zone files are dropped on to choose them for the 
upload of a field.
+     *
+     * @param zoneId the id of the element
+     * @param fieldId the id of the field
+     * @param dragOverClass the CSS class of the element while files are 
dragged over it
+     */
+    Wicket.FileUploadToResourceField.addDropZone = function (zoneId, fieldId, 
dragOverClass)
+    {
+        const zone = document.getElementById(zoneId);
+        if (!zone || zone.wicketUploadDropZone) {
+            return;
+        }
+        zone.wicketUploadDropZone = true;
+        // dragenter and dragleave fire for every element within the zone the 
files pass over
+        let depth = 0;
+        const draggingFiles = function (event) {
+            return event.dataTransfer && 
Array.prototype.indexOf.call(event.dataTransfer.types, 'Files') >= 0;
+        };
+        const leave = function () {
+            depth = 0;
+            zone.classList.remove(dragOverClass);
+        };
+        zone.addEventListener('dragenter', function (event) {
+            if (draggingFiles(event)) {
+                event.preventDefault();
+                depth++;
+                zone.classList.add(dragOverClass);
+            }
+        });
+        zone.addEventListener('dragover', function (event) {
+            if (draggingFiles(event)) {
+                event.preventDefault();
+                event.dataTransfer.dropEffect = 'copy';
+            }
+        });
+        zone.addEventListener('dragleave', function (event) {
+            if (draggingFiles(event) && --depth <= 0) {
+                leave();
+            }
+        });
+        zone.addEventListener('drop', function (event) {
+            if (draggingFiles(event)) {
+                event.preventDefault();
+                leave();
+                const field = Wicket.Timer[fieldId];
+                if (field) {
+                    field.drop(event.dataTransfer);
+                }
+            }
+        });
+    };
+
+    /**
+     * @param dataTransfer the data of a drop
+     * @returns {File[]} the files dropped, without folders
+     */
+    function droppedFiles(dataTransfer)
+    {
+        if (!dataTransfer.items) {
+            return Array.from(dataTransfer.files);
+        }
+        const files = [];
+        for (let index = 0; index < dataTransfer.items.length; index++) {
+            const item = dataTransfer.items[index];
+            const entry = item.kind === 'file' && item.webkitGetAsEntry ? 
item.webkitGetAsEntry() : null;
+            const file = item.kind === 'file' && !(entry && entry.isDirectory) 
? item.getAsFile() : null;
+            if (file) {
+                files.push(file);
+            }
+        }
+        return files;
+    }
+
+    /**
+     * @param accept the accept attribute of an input
+     * @param file a file
+     * @returns {boolean} whether the input accepts the file, by its extension 
or its type
+     */
+    function accepts(accept, file)
+    {
+        if (!accept) {
+            return true;
+        }
+        const name = file.name.toLowerCase();
+        const type = (file.type || '').toLowerCase();
+        return accept.split(',').some(function (token) {
+            token = token.trim().toLowerCase();
+            if (token.charAt(0) === '.') {
+                return name.endsWith(token);
+            }
+            if (token.endsWith('/*')) {
+                return type.startsWith(token.slice(0, -1));
+            }
+            return token !== '' && type === token;
+        });
+    }
+
+    /**
+     * Chooses files for the next upload: in addition to those chosen before 
if the field lists its
+     * files and its input is multiple, else instead of them.
+     *
+     * @param files the files
+     */
+    Wicket.FileUploadToResourceField.prototype.choose = function (files)
+    {
+        const input = document.getElementById(this.inputName);
+        this.files = this.hasLists() && input.multiple ? 
this.files.concat(files) : files;
+        this.updateInput();
+        this.renderLists();
+    };
+
+    /**
+     * Chooses the files dropped on a drop zone of the field as if they were 
chosen in the
+     * browser's dialog, and fires the input and change events of the input: 
those the input
+     * accepts, and only the first of them unless the input is multiple.
+     *
+     * @param dataTransfer the data of the drop
+     */
+    Wicket.FileUploadToResourceField.prototype.drop = function (dataTransfer)
+    {
+        const input = document.getElementById(this.inputName);
+        let files = droppedFiles(dataTransfer).filter(function (file) {
+            return accepts(input.accept, file);
+        });
+        if (!input.multiple) {
+            files = files.slice(0, 1);
+        }
+        if (files.length === 0) {
+            return;
+        }
+        this.choose(files);
+        this.dispatching = true;
+        try {
+            input.dispatchEvent(new Event('input', {'bubbles': true}));
+            input.dispatchEvent(new Event('change', {'bubbles': true}));
+        } finally {
+            this.dispatching = false;
+        }
+    };
+
+    /**
+     * Takes a file out of those chosen for the next upload.
+     *
+     * @param file the file
+     */
+    Wicket.FileUploadToResourceField.prototype.remove = function (file)
+    {
+        this.files = this.files.filter(function (chosen) {
+            return chosen !== file;
+        });
+        this.updateInput();
+        this.renderLists();
+    };
+
+    Wicket.FileUploadToResourceField.prototype.updateInput = function ()
+    {
+        const dataTransfer = new window.DataTransfer();
+        this.files.forEach(function (file) {
+            dataTransfer.items.add(file);
+        });
+        document.getElementById(this.inputName).files = dataTransfer.files;
+    };
+
+    /**
+     * @returns {boolean} whether a file list of the field is in the page
+     */
+    Wicket.FileUploadToResourceField.prototype.hasLists = function ()
+    {
+        return Object.keys(lists[this.inputName] || {}).some(function (listId) 
{
+            return document.getElementById(listId) !== null;
+        });
+    };
+
+    Wicket.FileUploadToResourceField.prototype.renderLists = function ()
+    {
+        const fieldLists = lists[this.inputName] || {};
+        Object.keys(fieldLists).forEach(function (listId) {
+            const list = document.getElementById(listId);
+            if (list) {
+                this.renderList(list, fieldLists[listId]);
+            }
+        }, this);
+    };
+
+    /**
+     * Renders an item for each chosen file into a list: its name, as text, 
and a button removing
+     * it, which is disabled while an upload runs.
+     *
+     * @param list the ul or ol
+     * @param removeText the text of the buttons
+     */
+    Wicket.FileUploadToResourceField.prototype.renderList = function (list, 
removeText)
+    {
+        const self = this;
+        list.replaceChildren();
+        this.files.forEach(function (file, index) {
+            const name = document.createElement('span');
+            name.className = 'wicket-upload-file-name';
+            name.id = list.id + '-' + index + '-name';
+            name.textContent = file.name;
+            const remove = document.createElement('button');
+            remove.type = 'button';
+            remove.className = 'wicket-upload-file-remove';
+            remove.id = list.id + '-' + index + '-remove';
+            remove.textContent = removeText;
+            remove.setAttribute('aria-labelledby', remove.id + ' ' + name.id);
+            remove.disabled = !!self.xhr;
+            remove.addEventListener('click', function () {
+                self.remove(file);
+            });
+            const item = document.createElement('li');
+            item.className = 'wicket-upload-file';
+            item.append(name, ' ', remove);
+            list.appendChild(item);
+        });
+    };
 
     /**
      * Uploads the files of the input, unless an upload of the field is 
running already, or the
@@ -99,7 +349,7 @@
             if (res.error) {
                 self.failed(res.errorMessage);
             } else {
-                delete self.xhr;
+                self.uploaded();
                 self.clientSideSuccessCallBack();
                 self.report({'error': false, 'filesInfo': 
JSON.stringify(res)});
             }
@@ -111,7 +361,7 @@
 
         xhr.onabort = function () {
             // user aborted the upload.
-            delete self.xhr;
+            self.ended();
             self.report({'error': true, 'errorMessage': UPLOAD_CANCELED});
         };
 
@@ -122,10 +372,33 @@
         }
 
         this.xhr = xhr;
+        this.sending = this.files.slice();
+        this.renderLists();
         xhr.send(formData);
         return true;
     };
 
+    Wicket.FileUploadToResourceField.prototype.ended = function ()
+    {
+        delete this.xhr;
+        this.renderLists();
+    };
+
+    /**
+     * Ends an upload that succeeded: the files sent leave the lists of the 
field.
+     */
+    Wicket.FileUploadToResourceField.prototype.uploaded = function ()
+    {
+        if (this.hasLists()) {
+            const sent = this.sending;
+            this.files = this.files.filter(function (file) {
+                return sent.indexOf(file) < 0;
+            });
+            this.updateInput();
+        }
+        this.ended();
+    };
+
     /**
      * @param files the files to upload
      * @returns {string|null} the key of the first limit of the field the 
files break, if any
@@ -158,7 +431,7 @@
      */
     Wicket.FileUploadToResourceField.prototype.failed = function (errorMessage)
     {
-        delete this.xhr;
+        this.ended();
         const ep = {'error': true, 'errorMessage': errorMessage};
         this.uploadErrorCallBack(ep);
         this.report(ep);
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
index 2073fe09c5..4ce9360867 100644
--- 
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
@@ -14,3 +14,4 @@
 #  limitations under the License.
 wicket.no.files.selected = Nothing to upload: no files were selected.
 uploadToResourceFailed = The upload failed.
+uploadToResourceRemove = Remove
diff --git a/wicket-core/src/test/js/all.html b/wicket-core/src/test/js/all.html
index 83f4085202..ef2f3ab0e1 100644
--- a/wicket-core/src/test/js/all.html
+++ b/wicket-core/src/test/js/all.html
@@ -160,6 +160,9 @@
                        FileUploadToResourceField
                -->
                <input type="file" id="uploadToResourceInput" 
multiple="multiple"/>
+               <div id="uploadToResourceDropZone"><input type="file" 
id="uploadToResourceListedInput" multiple="multiple"/></div>
+               <ul id="uploadToResourceList"></ul>
+               <input type="file" id="uploadToResourceSingleInput" 
accept=".txt,image/*"/>
 
                <div id="ajaxIndicator"></div>
        </div>
diff --git a/wicket-core/src/test/js/upload-to-resource.js 
b/wicket-core/src/test/js/upload-to-resource.js
index d43314ea20..dd31dca9fb 100644
--- a/wicket-core/src/test/js/upload-to-resource.js
+++ b/wicket-core/src/test/js/upload-to-resource.js
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-/*global QUnit: true, DataTransfer: true */
+/*global QUnit: true, DataTransfer: true, DragEvent: true */
 
 Wicket.Event.add(window, 'domready', function() {
        "use strict";
@@ -218,4 +218,183 @@ Wicket.Event.add(window, 'domready', function() {
                field.upload();
                assert.equal(FakeXhr.instances.length, 2, 'the field is not 
left busy');
        });
+
+       const LISTED_ID = 'uploadToResourceListedInput';
+       const SINGLE_ID = 'uploadToResourceSingleInput';
+       const LIST_ID = 'uploadToResourceList';
+       const ZONE_ID = 'uploadToResourceDropZone';
+
+       function newFieldOf(inputId) {
+               const field = newField({inputName: inputId});
+               Wicket.Timer[inputId] = field;
+               return field;
+       }
+
+       function pick(inputId, files) {
+               const dataTransfer = new DataTransfer();
+               files.forEach(file => dataTransfer.items.add(file));
+               const input = document.getElementById(inputId);
+               input.files = dataTransfer.files;
+               input.dispatchEvent(new Event('change', {bubbles: true}));
+       }
+
+       function drop(files) {
+               const dataTransfer = new DataTransfer();
+               files.forEach(file => dataTransfer.items.add(file));
+               document.getElementById(ZONE_ID).dispatchEvent(new 
DragEvent('drop',
+                       {dataTransfer: dataTransfer, bubbles: true, cancelable: 
true}));
+       }
+
+       function listed() {
+               return Array.from(document.querySelectorAll('#' + LIST_ID + ' 
.wicket-upload-file-name'))
+                       .map(name => name.textContent);
+       }
+
+       function inputFiles(inputId) {
+               return 
Array.from(document.getElementById(inputId).files).map(file => file.name);
+       }
+
+       function removeButtons() {
+               return Array.from(document.querySelectorAll('#' + LIST_ID + ' 
.wicket-upload-file-remove'));
+       }
+
+       module('Wicket.FileUploadToResourceField file lists and drop zones', {
+               beforeEach: function () {
+                       originalXhr = window.XMLHttpRequest;
+                       originalPost = Wicket.Ajax.post;
+                       FakeXhr.instances = [];
+                       window.XMLHttpRequest = FakeXhr;
+                       reports = [];
+                       calls = [];
+                       Wicket.Ajax.post = function (attrs) {
+                               reports.push(attrs);
+                       };
+               },
+               afterEach: function () {
+                       window.XMLHttpRequest = originalXhr;
+                       Wicket.Ajax.post = originalPost;
+                       delete Wicket.Timer[LISTED_ID];
+                       delete Wicket.Timer[SINGLE_ID];
+               }
+       });
+
+       test('without a list, a choice replaces the files chosen before', 
assert => {
+               newFieldOf(LISTED_ID);
+
+               pick(LISTED_ID, [newFile('a.txt', 1)]);
+               pick(LISTED_ID, [newFile('b.txt', 1)]);
+
+               assert.deepEqual(inputFiles(LISTED_ID), ['b.txt']);
+       });
+
+       test('a list shows the files of several choices, which add up', assert 
=> {
+               newFieldOf(LISTED_ID);
+               Wicket.FileUploadToResourceField.addList(LISTED_ID, LIST_ID, 
'Remove');
+
+               pick(LISTED_ID, [newFile('a.txt', 1)]);
+               pick(LISTED_ID, [newFile('b.txt', 1), newFile('c.txt', 1)]);
+
+               assert.deepEqual(listed(), ['a.txt', 'b.txt', 'c.txt']);
+               assert.deepEqual(inputFiles(LISTED_ID), ['a.txt', 'b.txt', 
'c.txt']);
+       });
+
+       test('a list added after the field shows the files chosen before', 
assert => {
+               newFieldOf(LISTED_ID);
+               pick(LISTED_ID, [newFile('a.txt', 1)]);
+
+               Wicket.FileUploadToResourceField.addList(LISTED_ID, LIST_ID, 
'Remove');
+
+               assert.deepEqual(listed(), ['a.txt']);
+       });
+
+       test('the name of a file is listed as text', assert => {
+               newFieldOf(LISTED_ID);
+               Wicket.FileUploadToResourceField.addList(LISTED_ID, LIST_ID, 
'Remove');
+
+               pick(LISTED_ID, [newFile('<b>bold</b>.txt', 1)]);
+
+               assert.deepEqual(listed(), ['<b>bold</b>.txt']);
+               assert.equal(document.querySelectorAll('#' + LIST_ID + ' 
b').length, 0);
+       });
+
+       test('a button removes its file', assert => {
+               newFieldOf(LISTED_ID);
+               Wicket.FileUploadToResourceField.addList(LISTED_ID, LIST_ID, 
'Remove');
+               pick(LISTED_ID, [newFile('a.txt', 1), newFile('b.txt', 1)]);
+
+               const button = removeButtons()[1];
+               assert.equal(button.textContent, 'Remove');
+               assert.equal(button.getAttribute('aria-labelledby'),
+                       button.id + ' ' + document.querySelectorAll('#' + 
LIST_ID + ' .wicket-upload-file-name')[1].id);
+               button.click();
+
+               assert.deepEqual(listed(), ['a.txt']);
+               assert.deepEqual(inputFiles(LISTED_ID), ['a.txt']);
+       });
+
+       test('the files sent leave the list once uploaded, those chosen 
meanwhile stay', assert => {
+               const field = newFieldOf(LISTED_ID);
+               Wicket.FileUploadToResourceField.addList(LISTED_ID, LIST_ID, 
'Remove');
+               pick(LISTED_ID, [newFile('a.txt', 1)]);
+
+               field.upload();
+               assert.true(removeButtons().every(button => button.disabled), 
'no removing while uploading');
+               pick(LISTED_ID, [newFile('b.txt', 1)]);
+               FakeXhr.instances[0].respond(200, '[]');
+
+               assert.deepEqual(listed(), ['b.txt']);
+               assert.deepEqual(inputFiles(LISTED_ID), ['b.txt']);
+               assert.true(removeButtons().every(button => !button.disabled), 
'removing once the upload ended');
+       });
+
+       test('a failed upload keeps its files in the list', assert => {
+               const field = newFieldOf(LISTED_ID);
+               Wicket.FileUploadToResourceField.addList(LISTED_ID, LIST_ID, 
'Remove');
+               pick(LISTED_ID, [newFile('a.txt', 1)]);
+
+               field.upload();
+               FakeXhr.instances[0].respond(500, 'Internal Server Error');
+
+               assert.deepEqual(listed(), ['a.txt']);
+       });
+
+       test('dropped files are chosen, and the input fires its events', assert 
=> {
+               newFieldOf(LISTED_ID);
+               Wicket.FileUploadToResourceField.addList(LISTED_ID, LIST_ID, 
'Remove');
+               Wicket.FileUploadToResourceField.addDropZone(ZONE_ID, 
LISTED_ID, 'dragging');
+               pick(LISTED_ID, [newFile('a.txt', 1)]);
+               const events = [];
+               const input = document.getElementById(LISTED_ID);
+               input.addEventListener('input', () => events.push('input:' + 
input.files.length));
+               input.addEventListener('change', () => events.push('change:' + 
input.files.length));
+
+               drop([newFile('b.txt', 1), newFile('c.txt', 1)]);
+
+               assert.deepEqual(listed(), ['a.txt', 'b.txt', 'c.txt']);
+               assert.deepEqual(events, ['input:3', 'change:3']);
+       });
+
+       test('a drop zone chooses only the files the input accepts, one for a 
single input', assert => {
+               newFieldOf(SINGLE_ID);
+               Wicket.FileUploadToResourceField.addDropZone(ZONE_ID, 
SINGLE_ID, 'dragging');
+
+               drop([new File(['x'], 'a.pdf', {type: 'application/pdf'}),
+                       new File(['x'], 'b.png', {type: 'image/png'}),
+                       new File(['x'], 'c.txt', {type: 'text/plain'})]);
+
+               assert.deepEqual(inputFiles(SINGLE_ID), ['b.png']);
+       });
+
+       test('a drop zone is marked while files are dragged over it', assert => 
{
+               newFieldOf(LISTED_ID);
+               Wicket.FileUploadToResourceField.addDropZone(ZONE_ID, 
LISTED_ID, 'dragging');
+               const zone = document.getElementById(ZONE_ID);
+               const dataTransfer = new DataTransfer();
+               dataTransfer.items.add(newFile('a.txt', 1));
+
+               zone.dispatchEvent(new DragEvent('dragenter', {dataTransfer: 
dataTransfer, bubbles: true, cancelable: true}));
+               assert.true(zone.classList.contains('dragging'), 'marked');
+               zone.dispatchEvent(new DragEvent('dragleave', {dataTransfer: 
dataTransfer, bubbles: true, cancelable: true}));
+               assert.false(zone.classList.contains('dragging'), 'unmarked');
+       });
 });
diff --git 
a/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadToResourcePage.css
 
b/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadToResourcePage.css
new file mode 100644
index 0000000000..e6ee56bf73
--- /dev/null
+++ 
b/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadToResourcePage.css
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+legend { border: 1px solid #e9601a; background-color: #bbb; color: #fff; 
padding: 4px; }
+fieldset { border: 1px solid #e9601a; padding: 10px; margin-top: 10px; }
+.upload-drop-zone { border: 2px dashed #bbb; padding: 10px; margin-bottom: 
10px; }
+.upload-drop-zone.wicket-upload-dragover { border-color: #e9601a; 
background-color: #fdf0e8; }
+.file-list:empty { display: none; }
+.wicket-upload-file-remove { margin-left: 0.5em; }
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
index bcccdf6a5b..b3536c16b5 100644
--- 
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
@@ -1,20 +1,18 @@
 <html xmlns:wicket="http://wicket.apache.org";>
-<head>
-       <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>
 
                <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>
+                       <div wicket:id="dropZone" class="upload-drop-zone">
+                               <p>Drop files here, or choose them, as often as 
you like:</p>
+                               <p>
+                                       <label 
wicket:for="fileInput">Files</label>
+                                       <input wicket:id="fileInput" 
type="file" multiple="multiple"/>
+                               </p>
+                               <ul wicket:id="fileList" class="file-list"></ul>
+                       </div>
                        <form wicket:id="form">
                                <p>
                                        <input 
wicket:id="allowToLeavePageWhileUploading" type="checkbox"/>
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
index f929b17401..fc5f4af59e 100644
--- 
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
@@ -26,17 +26,21 @@ 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.CssHeaderItem;
 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.FileDropZoneBehavior;
+import org.apache.wicket.markup.html.form.upload.resource.FileListBehavior;
 import 
org.apache.wicket.markup.html.form.upload.resource.FileUploadToResourceField;
+import org.apache.wicket.markup.html.form.upload.resource.IUploadsFileManager;
 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;
+import org.apache.wicket.request.resource.CssResourceReference;
 import org.apache.wicket.util.lang.Bytes;
 
 /**
@@ -156,7 +160,13 @@ public class UploadToResourcePage extends WicketExamplePage
                fileUploadToResourceField.setMaxSize(Bytes.kilobytes(100));
                fileUploadToResourceField.setFileMaxSize(Bytes.kilobytes(90));
                fileUploadToResourceField.setFileCountMax(5L);
-               add(fileUploadToResourceField);
+
+               WebMarkupContainer dropZone = new 
WebMarkupContainer("dropZone");
+               dropZone.add(new 
FileDropZoneBehavior(fileUploadToResourceField));
+               add(dropZone);
+               dropZone.add(fileUploadToResourceField);
+               dropZone.add(new WebMarkupContainer("fileList")
+                       .add(new FileListBehavior(fileUploadToResourceField)));
 
                Form<Void> form = new Form<>("form");
                add(form);
@@ -254,8 +264,12 @@ public class UploadToResourcePage extends WicketExamplePage
        @Override
        public void renderHead(IHeaderResponse response) {
                super.renderHead(response);
-               response.render(
-                               
OnDomReadyHeaderItem.forScript("$(window).bind('beforeunload', function(){ if 
(Wicket.CurrentUpload != null) { return 'leave?'; }});"));
+               response.render(CssHeaderItem.forReference(
+                       new CssResourceReference(UploadToResourcePage.class, 
"UploadToResourcePage.css")));
+               response.render(OnDomReadyHeaderItem.forScript(
+                       "Wicket.Event.add(window, 'beforeunload', function 
(event) {" +
+                               " if (Wicket.CurrentUpload != null) { 
event.preventDefault(); event.returnValue = 'leave?'; }" +
+                               "});"));
        }
 
        @Override
diff --git 
a/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadToResourcePage.properties
 
b/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadToResourcePage.properties
index 25e21b65ea..7741caad74 100644
--- 
a/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadToResourcePage.properties
+++ 
b/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadToResourcePage.properties
@@ -13,5 +13,5 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 title=Multiple (AJAX) Upload to a Resource
-explanation=<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>
+explanation=<p>Wicket can upload to a <em>mounted resource.</em></p> <p>This 
upload is done with a plain XMLHttpRequest and <em>does not</em> block Wicket 
AJAX</p> <p>Files chosen several times or dropped on the zone add up in a list, 
from which each can be removed before the upload</p> <p><em>Upload 
progressbar</em> has been updated to work with this component</p>
 order=3

Reply via email to