You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by sv...@apache.org on 2019/07/10 17:06:45 UTC
[wicket] 01/01: WICKET-6666 modal dialog
This is an automated email from the ASF dual-hosted git repository.
svenmeier pushed a commit to branch WICKET-6666_modal-dialog-2
in repository https://gitbox.apache.org/repos/asf/wicket.git
commit 557d955fd93516ea0ed6b35c9e72a61b3e7bf2a1
Author: Sven Meier <sv...@apache.org>
AuthorDate: Wed Jun 19 18:09:44 2019 +0200
WICKET-6666 modal dialog
ModalWindow successor
---
.../wicket/ajax/res/js/wicket-ajax-jquery.js | 25 ++
.../examples/ajax/builtin/AjaxApplication.java | 2 +
.../apache/wicket/examples/ajax/builtin/Index.html | 2 +
.../ajax/builtin/modal/ModalDialogPage.html | 63 +++++
.../ajax/builtin/modal/ModalDialogPage.java | 180 +++++++++++++
.../ajax/builtin/modal/MyDialogLayout.html | 34 +++
.../ajax/builtin/modal/MyDialogLayout.java | 38 +++
.../ajax/markup/html/modal/ModalDialog.html | 26 ++
.../ajax/markup/html/modal/ModalDialog.java | 281 +++++++++++++++++++++
.../ajax/markup/html/modal/TrapFocusBehavior.java | 50 ++++
.../ajax/markup/html/modal/theme/DefaultTheme.java | 50 ++++
.../ajax/markup/html/modal/theme/theme.css | 88 +++++++
.../ajax/markup/html/modal/trap-focus.js | 116 +++++++++
.../ajax/markup/html/repeater/AjaxListPanel.html | 22 ++
.../ajax/markup/html/repeater/AjaxListPanel.java | 114 +++++++++
15 files changed, 1091 insertions(+)
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/res/js/wicket-ajax-jquery.js b/wicket-core/src/main/java/org/apache/wicket/ajax/res/js/wicket-ajax-jquery.js
index 47ed601..dec21cd 100644
--- a/wicket-core/src/main/java/org/apache/wicket/ajax/res/js/wicket-ajax-jquery.js
+++ b/wicket-core/src/main/java/org/apache/wicket/ajax/res/js/wicket-ajax-jquery.js
@@ -1507,6 +1507,31 @@
we.publish(topic.DOM_NODE_ADDED, newElement);
}
},
+
+ add: function (element, text) {
+ var we = Wicket.Event;
+ var topic = we.Topic;
+
+ // jQuery 1.9+ expects '<' as the very first character in text
+ var cleanedText = jQuery.trim(text);
+
+ var $newElement = jQuery(cleanedText);
+ jQuery(element).append($newElement);
+
+ var newElement = Wicket.$(element.id);
+ if (newElement) {
+ we.publish(topic.DOM_NODE_ADDED, newElement);
+ }
+ },
+
+ remove: function (element) {
+ var we = Wicket.Event;
+ var topic = we.Topic;
+
+ we.publish(topic.DOM_NODE_REMOVING, element);
+
+ jQuery(element).remove();
+ },
// Method for serializing DOM nodes to string
// original taken from Tacos (http://tacoscomponents.jot.com)
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/AjaxApplication.java b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/AjaxApplication.java
index b43828e..01d3cd5 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/AjaxApplication.java
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/AjaxApplication.java
@@ -21,6 +21,7 @@ import org.apache.wicket.Page;
import org.apache.wicket.ajax.AjaxNewWindowNotifyingBehavior;
import org.apache.wicket.application.IComponentInitializationListener;
import org.apache.wicket.examples.WicketExampleApplication;
+import org.apache.wicket.examples.ajax.builtin.modal.ModalDialogPage;
import org.apache.wicket.examples.ajax.builtin.modal.ModalWindowPage;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.response.filter.AjaxServerAndClientTimeFilter;
@@ -63,6 +64,7 @@ public class AjaxApplication extends WicketExampleApplication
mountPage("lazy-loading", LazyLoadingPage.class);
mountPage("links", LinksPage.class);
mountPage("modal-window", ModalWindowPage.class);
+ mountPage("modal-dialog", ModalDialogPage.class);
mountPage("on-change-ajax-behavior", OnChangeAjaxBehaviorPage.class);
mountPage("pageables", PageablesPage.class);
mountPage("ratings", RatingsPage.class);
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/Index.html b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/Index.html
index 2da2160..30aa04c 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/Index.html
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/Index.html
@@ -24,6 +24,8 @@
<br/><br/>
<a href="modal/ModalWindowPage.html">Modal window</a>: javascript modal window example
<br/><br/>
+<a href="modal/ModalDialogPage.html">Modal dialog (replaces deprecated Modal Window)</a>: javascript modal dialog example
+<br/><br/>
<a href="OnChangeAjaxBehaviorPage.html">On Change Ajax Updater Example</a>: demonstrates updating page with ajax when text field value is changed
<br/><br/>
<a href="PageablesPage.html">Pageables Example</a>: shows ajax paging
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/modal/ModalDialogPage.html b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/modal/ModalDialogPage.html
new file mode 100644
index 0000000..bf34917
--- /dev/null
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/modal/ModalDialogPage.html
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<html>
+ <head>
+ <wicket:head>
+ <style>
+ .modal-dialog {
+ border-radius: 5px;
+ overflow: hidden;
+ }
+
+ h1.example-header {
+ background: #ffb158;
+ padding-top: 4px;
+ text-align: center;
+ }
+
+ .example-content {
+ padding: 20px;
+ min-width: 100px;
+ min-height: 100px;
+ overflow-x: hidden;
+ overflow-y: auto;
+ resize: both;
+ }
+ </style>
+ </wicket:head>
+ </head>
+ <body>
+ <wicket:extend xmlns:wicket="http://wicket.apache.org">
+
+ <p wicket:id="stacked">
+ <input type="radio" wicket:id="no" /> <label wicket:for="no">nested</label>
+ <input type="radio" wicket:id="yes" /> <label wicket:for="yes">stacked dialogs</label>
+ </p>
+
+ <div wicket:id="start"></div>
+
+ <div wicket:id="stackedDialogs"></div>
+
+ <wicket:fragment wicket:id="fragment">
+ <p>
+ <input type="text" wicket:id="text" placeholder="Focus and press ENTER to open dialog" size="32" autofocus="autofocus"/>
+ </p>
+
+ <div wicket:id="nestedDialog"></div>
+
+ <p>
+ <a wicket:id="openDialog">Open dialog</a>
+ |
+ <a wicket:id="ajaxOpenDialog">via Ajax</a>
+
+ <span wicket:id="closing">
+ |
+ <a wicket:id="close">Close dialog</a>
+ |
+ via Ajax by pressing ESC
+ </span>
+ </p>
+ </wicket:fragment>
+
+ </wicket:extend>
+ </body>
+</html>
\ No newline at end of file
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/modal/ModalDialogPage.java b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/modal/ModalDialogPage.java
new file mode 100644
index 0000000..aad0eca
--- /dev/null
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/modal/ModalDialogPage.java
@@ -0,0 +1,180 @@
+/*
+ * 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.ajax.builtin.modal;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.ajax.AjaxEventBehavior;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.attributes.AjaxCallListener;
+import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
+import org.apache.wicket.ajax.form.AjaxFormChoiceComponentUpdatingBehavior;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import org.apache.wicket.examples.ajax.builtin.BasePage;
+import org.apache.wicket.extensions.ajax.markup.html.modal.ModalDialog;
+import org.apache.wicket.extensions.ajax.markup.html.modal.theme.DefaultTheme;
+import org.apache.wicket.extensions.ajax.markup.html.repeater.AjaxListPanel;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.form.Radio;
+import org.apache.wicket.markup.html.form.RadioGroup;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.markup.html.link.Link;
+import org.apache.wicket.markup.html.panel.Fragment;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.PropertyModel;
+
+/**
+ * @author Igor Vaynberg (ivaynberg)
+ */
+public class ModalDialogPage extends BasePage
+{
+
+ private AjaxListPanel stackedDialogs;
+
+ /**
+ * Should dialogs be stacked rather than nested
+ */
+ private boolean stacked = false;
+
+ public ModalDialogPage()
+ {
+
+ queue(new RadioGroup("stacked", new PropertyModel<>(this, "stacked"))
+ .setRenderBodyOnly(false).add(new AjaxFormChoiceComponentUpdatingBehavior()
+ {
+ @Override
+ protected void onUpdate(AjaxRequestTarget target)
+ {
+ }
+ }));
+
+ queue(new Radio<Boolean>("yes", Model.of(true)));
+ queue(new Radio<Boolean>("no", Model.of(false)));
+
+ queue(new ModalFragment("start"));
+
+ stackedDialogs = new AjaxListPanel("stackedDialogs");
+ queue(stackedDialogs);
+ }
+
+ private class ModalFragment extends Fragment
+ {
+
+ private ModalDialog nestedDialog;
+
+ public ModalFragment(String id)
+ {
+ super(id, "fragment", ModalDialogPage.this);
+
+ nestedDialog = new ModalDialog("nestedDialog");
+ nestedDialog.add(new DefaultTheme());
+ nestedDialog.trapFocus();
+ nestedDialog.closeOnEscape();
+ queue(nestedDialog);
+
+ queue(new AjaxLink<Void>("ajaxOpenDialog")
+ {
+ @Override
+ public void onClick(AjaxRequestTarget target)
+ {
+ openDialog(target);
+ }
+ });
+
+ queue(new Link<Void>("openDialog")
+ {
+ @Override
+ public void onClick()
+ {
+ openDialog(null);
+ }
+ });
+
+ queue(new TextField("text").add(new AjaxEventBehavior("keydown")
+ {
+ @Override
+ protected void updateAjaxAttributes(AjaxRequestAttributes attributes)
+ {
+ super.updateAjaxAttributes(attributes);
+
+ attributes.getAjaxCallListeners().add(new AjaxCallListener()
+ {
+ @Override
+ public CharSequence getPrecondition(Component component)
+ {
+ return "return Wicket.Event.keyCode(attrs.event) == 13;";
+ }
+ });
+ }
+
+ @Override
+ protected void onEvent(AjaxRequestTarget target)
+ {
+ openDialog(target);
+ }
+ }));
+
+ queue(new WebMarkupContainer("closing")
+ {
+ @Override
+ protected void onConfigure()
+ {
+ super.onConfigure();
+
+ setVisible(findParent(ModalDialog.class) != null);
+ }
+ });
+
+ queue(new Link<Void>("close")
+ {
+ @Override
+ public void onClick()
+ {
+ findParent(ModalDialog.class).close(null);
+ }
+ });
+ }
+
+ private void openDialog(AjaxRequestTarget target)
+ {
+ Component content = new MyDialogLayout(ModalDialog.CONTENT_ID, Model.of("Dialog"),
+ new ModalFragment(ModalDialog.CONTENT_ID));
+
+ if (stacked)
+ {
+ // stack a new dialog
+ ModalDialog dialog = new ModalDialog(stackedDialogs.newChildId())
+ {
+ @Override
+ public ModalDialog close(AjaxRequestTarget target)
+ {
+ return stackedDialogs.delete(this, target);
+ }
+ };
+ dialog.add(new DefaultTheme());
+ dialog.trapFocus();
+ dialog.closeOnEscape();
+ dialog.setContent(content);
+ stackedDialogs.append(dialog, target).open(target);
+ }
+ else
+ {
+ // use the nested dialog
+ nestedDialog.open(content, target);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/modal/MyDialogLayout.html b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/modal/MyDialogLayout.html
new file mode 100644
index 0000000..56af543
--- /dev/null
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/modal/MyDialogLayout.html
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<html>
+ <head>
+ <wicket:head>
+ <style>
+ .modal-dialog {
+ border-radius: 5px;
+ overflow: hidden;
+ }
+
+ h1.example-header {
+ background: #ffb158;
+ padding-top: 4px;
+ text-align: center;
+ }
+
+ .example-content {
+ padding: 20px;
+ min-width: 100px;
+ min-height: 100px;
+ overflow-x: hidden;
+ overflow-y: auto;
+ resize: both;
+ }
+ </style>
+ </wicket:head>
+ </head>
+ <body>
+ <wicket:panel xmlns:wicket="http://wicket.apache.org">
+ <h1 wicket:id="header" class="example-header"></h1>
+ <div wicket:id="content" class="example-content"></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/modal/MyDialogLayout.java b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/modal/MyDialogLayout.java
new file mode 100644
index 0000000..506447e
--- /dev/null
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/modal/MyDialogLayout.java
@@ -0,0 +1,38 @@
+/*
+ * 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.ajax.builtin.modal;
+
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.IModel;
+
+/**
+ * @author svenmeier
+ */
+public class MyDialogLayout extends Panel
+{
+
+ public MyDialogLayout(String id, IModel<String> header, WebMarkupContainer content)
+ {
+ super(id);
+
+ add(new Label("header", header));
+
+ add(content);
+ }
+}
\ No newline at end of file
diff --git a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/modal/ModalDialog.html b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/modal/ModalDialog.html
new file mode 100644
index 0000000..35b7bef
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/modal/ModalDialog.html
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+ 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:panel xmlns:wicket="http://wicket.apache.org">
+ <div wicket:id="overlay" class="modal-dialog-overlay">
+ <div wicket:id="dialog" class='modal-dialog'>
+ <div class='modal-dialog-content'>
+ <div wicket:id="content"></div>
+ </div>
+ </div>
+ </div>
+</wicket:panel>
\ No newline at end of file
diff --git a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/modal/ModalDialog.java b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/modal/ModalDialog.java
new file mode 100644
index 0000000..ce88ae4
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/modal/ModalDialog.java
@@ -0,0 +1,281 @@
+/*
+ * 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.extensions.ajax.markup.html.modal;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.ajax.AjaxEventBehavior;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.attributes.AjaxCallListener;
+import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
+import org.apache.wicket.ajax.attributes.AjaxRequestAttributes.EventPropagation;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.panel.Panel;
+
+/**
+ * Presents a modal dialog to the user. See {@link #open(Component, AjaxRequestTarget)} and {@link #close(AjaxRequestTarget)} methods.
+ *
+ * @author Igor Vaynberg (ivaynberg)
+ * @author svenmeier
+ */
+public class ModalDialog extends Panel
+{
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String OVERLAY_ID = "overlay";
+
+ private static final String DIALOG_ID = "dialog";
+
+ public static final String CONTENT_ID = "content";
+
+ private WebMarkupContainer overlay;
+
+ private WebMarkupContainer dialog;
+
+ private boolean removeContentOnClose;
+
+ public ModalDialog(String id)
+ {
+ super(id);
+
+ setOutputMarkupId(true);
+
+ overlay = newOverlay(OVERLAY_ID);
+ overlay.setVisible(false);
+ add(overlay);
+
+ dialog = newDialog(DIALOG_ID);
+ overlay.add(dialog);
+ }
+
+ /**
+ * Factory method for the overlay markup around the dialog.
+ *
+ * @param overlayId
+ * id
+ * @return overlay
+ */
+ protected WebMarkupContainer newOverlay(String overlayId)
+ {
+ return new WebMarkupContainer(overlayId);
+ }
+
+ /**
+ * Factory method for the dialog markup around the content.
+ *
+ * @param overlayId
+ * id
+ * @return overlay
+ */
+ protected WebMarkupContainer newDialog(String dialogId)
+ {
+ return new WebMarkupContainer(dialogId);
+ }
+
+ /**
+ * Set a content.
+ *
+ * @param content
+ *
+ * @see #open(AjaxRequestTarget)
+ */
+ public void setContent(Component content)
+ {
+ if (!content.getId().equals(CONTENT_ID))
+ {
+ throw new IllegalArgumentException(
+ "Content must have wicket id set to ModalDialog.CONTENT_ID");
+ }
+
+ dialog.addOrReplace(content);
+
+ removeContentOnClose = false;
+ }
+
+ /**
+ * Open the dialog with a content.
+ * <p>
+ * The content will be removed on close of the dialog.
+ *
+ * @param content
+ * the content
+ * @param target
+ * an optional Ajax target
+ * @return this
+ *
+ * @see #close(AjaxRequestTarget)
+ */
+ public ModalDialog open(Component content, AjaxRequestTarget target)
+ {
+ setContent(content);
+ removeContentOnClose = true;
+
+ overlay.setVisible(true);
+
+ if (target != null)
+ {
+ target.add(this);
+ }
+
+ return this;
+ }
+
+ /**
+ * Open the dialog.
+ *
+ * @param target
+ * an optional Ajax target
+ * @return this
+ *
+ * @see #setContent(Component)
+ */
+ public ModalDialog open(AjaxRequestTarget target)
+ {
+ if (overlay.size() == 0) {
+ throw new WicketRuntimeException("no content set");
+ }
+
+ overlay.setVisible(true);
+
+ if (target != null)
+ {
+ target.add(this);
+ }
+
+ return this;
+ }
+
+ /**
+ * Is this dialog open.
+ *
+ * @return <code>true</code> if open
+ */
+ public boolean isOpen()
+ {
+ return overlay.isVisible();
+ }
+
+ /**
+ * Close this dialog.
+ * <p>
+ * If opened via {@link #open(Component, AjaxRequestTarget)}, the content is removed from the component tree
+ *
+ * @param target
+ * an optional Ajax target
+ * @return this
+ *
+ * @see #open(Component, AjaxRequestTarget)
+ */
+ public ModalDialog close(AjaxRequestTarget target)
+ {
+ overlay.setVisible(false);
+ if (removeContentOnClose) {
+ dialog.removeAll();
+ }
+
+ if (target != null)
+ {
+ target.add(this);
+ }
+
+ return this;
+ }
+
+ /**
+ * Close this dialog on press of escape key.
+ *
+ * @return this
+ */
+ public ModalDialog closeOnEscape()
+ {
+ overlay.add(new CloseBehavior("keydown")
+ {
+ protected CharSequence getPrecondition()
+ {
+ return "return Wicket.Event.keyCode(attrs.event) == 27";
+ }
+ });
+ return this;
+ }
+
+ /**
+ * Close this dialog on click outside.
+ *
+ * @return this
+ */
+ public ModalDialog closeOnClick()
+ {
+ overlay.add(new CloseBehavior("click") {
+ protected CharSequence getPrecondition()
+ {
+ return String.format("return attrs.event.target.id == '%s';", overlay.getMarkupId());
+ }
+ });
+ return this;
+ }
+
+ /**
+ * Convenience method to trap focus inside the overlay.
+ *
+ * @see {@link TrapFocusBehavior}
+ *
+ * @return this
+ */
+ public ModalDialog trapFocus()
+ {
+ overlay.add(new TrapFocusBehavior());
+
+ return this;
+ }
+
+ private abstract class CloseBehavior extends AjaxEventBehavior
+ {
+ private CloseBehavior(String event)
+ {
+ super(event);
+ }
+
+ @Override
+ protected void updateAjaxAttributes(AjaxRequestAttributes attributes)
+ {
+ super.updateAjaxAttributes(attributes);
+
+ // has to stop immediately to prevent an enclosing dialog to close too
+ attributes.setEventPropagation(EventPropagation.STOP_IMMEDIATE);
+
+ attributes.getAjaxCallListeners().add(new AjaxCallListener()
+ {
+ @Override
+ public CharSequence getPrecondition(Component component)
+ {
+ return CloseBehavior.this.getPrecondition();
+ }
+ });
+ }
+
+ protected CharSequence getPrecondition() {
+ return "";
+ }
+
+ @Override
+ protected void onEvent(AjaxRequestTarget target)
+ {
+ close(target);
+ }
+ }
+}
\ No newline at end of file
diff --git a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/modal/TrapFocusBehavior.java b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/modal/TrapFocusBehavior.java
new file mode 100644
index 0000000..3c37226
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/modal/TrapFocusBehavior.java
@@ -0,0 +1,50 @@
+/*
+ * 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.extensions.ajax.markup.html.modal;
+
+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.markup.head.PriorityHeaderItem;
+import org.apache.wicket.request.resource.JavaScriptResourceReference;
+import org.apache.wicket.request.resource.ResourceReference;
+
+/**
+ * Trap focus inside a component's markup.
+ *
+ * @author svenmeier
+ */
+public class TrapFocusBehavior extends Behavior
+{
+
+ private static final long serialVersionUID = 1L;
+
+ private static final ResourceReference JS = new JavaScriptResourceReference(
+ TrapFocusBehavior.class, "trap-focus.js");
+
+ @Override
+ public void renderHead(Component component, IHeaderResponse response)
+ {
+ response.render(JavaScriptHeaderItem.forReference(JS));
+
+ CharSequence script = String.format("Wicket.trapFocus('%s');", component.getMarkupId());
+
+ response.render(new PriorityHeaderItem(OnDomReadyHeaderItem.forScript(script)));
+ }
+}
diff --git a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/modal/theme/DefaultTheme.java b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/modal/theme/DefaultTheme.java
new file mode 100644
index 0000000..f408d07
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/modal/theme/DefaultTheme.java
@@ -0,0 +1,50 @@
+/*
+ * 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.extensions.ajax.markup.html.modal.theme;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.behavior.Behavior;
+import org.apache.wicket.extensions.ajax.markup.html.modal.ModalDialog;
+import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.head.CssHeaderItem;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.request.resource.CssResourceReference;
+import org.apache.wicket.request.resource.ResourceReference;
+
+/**
+ * Default theme for {@link ModalDialog}.
+ *
+ * @author svenmeier
+ */
+public class DefaultTheme extends Behavior
+{
+ private static final long serialVersionUID = 1L;
+
+ private static final ResourceReference CSS = new CssResourceReference(DefaultTheme.class, "theme.css");
+
+ @Override
+ public void onComponentTag(Component component, ComponentTag tag)
+ {
+ tag.append("class", "dialog-theme-default", " ");
+ }
+
+ @Override
+ public void renderHead(Component component, IHeaderResponse response)
+ {
+ response.render(CssHeaderItem.forReference(CSS));
+ }
+}
diff --git a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/modal/theme/theme.css b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/modal/theme/theme.css
new file mode 100644
index 0000000..1b99eae
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/modal/theme/theme.css
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+.dialog-theme-default .modal-dialog-overlay {
+ position: fixed;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ margin: 0;
+ padding: 0;
+ z-index: 1000;
+
+ /* flex-box to center .modal-dialog */
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+}
+
+.dialog-theme-default .modal-dialog-overlay::before {
+ content: "";
+ display: block;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ margin: 0;
+
+ background: rgba(0, 0, 0, 0.2);
+}
+
+.dialog-theme-default .modal-dialog {
+ position: absolute;
+ top: 10%;
+
+ background: white;
+ box-shadow: 0 0 60px 10px rgba(0, 0, 0, 0.7);
+}
+
+.dialog-theme-default .modal-dialog-content {
+}
+
+/* shift nested dialogs */
+
+.dialog-theme-default .modal-dialog .modal-dialog {
+ margin-top: 32px;
+ margin-left: 32px;
+}
+
+.dialog-theme-default .modal-dialog .modal-dialog .modal-dialog {
+ margin-top: 64px;
+ margin-left: 64px;
+}
+
+.dialog-theme-default .modal-dialog .modal-dialog .modal-dialog .modal-dialog {
+ margin-top: 96px;
+ margin-left: 96px;
+}
+
+/* shift sibling dialogs */
+
+.dialog-theme-default ~ .dialog-theme-default .modal-dialog {
+ margin-top: 32px;
+ margin-left: 32px;
+}
+
+.dialog-theme-default ~ .dialog-theme-default ~ .dialog-theme-default .modal-dialog {
+ margin-top: 64px;
+ margin-left: 64px;
+}
+
+.dialog-theme-default ~ .dialog-theme-default ~ .dialog-theme-default ~ .dialog-theme-default .modal-dialog {
+ margin-top: 96px;
+ margin-left: 96px;
+}
\ No newline at end of file
diff --git a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/modal/trap-focus.js b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/modal/trap-focus.js
new file mode 100644
index 0000000..eeb0539
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/modal/trap-focus.js
@@ -0,0 +1,116 @@
+/*
+ * 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($, window, document, undefined) {
+ 'use strict';
+
+ if (window.Wicket && window.Wicket.trapFocus) {
+ return;
+ }
+
+ /** Finds all elements inside container that can receive focus */
+ function findFocusable(container) {
+ var focusables = 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]';
+ return container.find(focusables).filter(":visible, *:not([tabindex=-1])");
+ }
+
+ // special handler listening for 'trapfocusremove' handlers - it is
+ // invoked when any element with that event handler is removed from the DOM
+ $.event.special.trapfocusremove = {
+ remove: function(handleObj) {
+ // forward notification of removal
+ handleObj.handler();
+ }
+ };
+
+ // global handler for 'focusin'
+ var focusin = $.noop;
+
+ // setup focus trap for an element
+ window.Wicket.trapFocus = function(element) {
+
+ // keep old active element
+ var oldActive = document.activeElement;
+ Wicket.Log.debug("trap-focus: focus was on element", oldActive);
+
+ var $element = $('#' + element);
+
+ // allow focus on element itself
+ $element.attr('tabindex', 0);
+
+ // handles focus navigation via tab key
+ $element.on("keydown", function(e) {
+ if (Wicket.Event.keyCode(e) === 9) { // tab
+ var $focusable = findFocusable($element);
+ if ($focusable.length > 0) {
+ var firstFocusable = $focusable.get(0);
+ var lastFocusable = $focusable.get($focusable.length - 1);
+
+ if (e.shiftKey) {
+ if (e.target === firstFocusable || $element.is(e.target)) {
+ e.preventDefault();
+ lastFocusable.focus();
+ }
+ } else {
+ if (e.target === lastFocusable || $element.is(e.target)) {
+ e.preventDefault();
+ firstFocusable.focus();
+ }
+ }
+ }
+ }
+ });
+
+ // turn off possible previous 'focusin' handler
+ var previousfocusin = focusin;
+ $(document).off("focusin", focusin);
+
+ // ... pull in focus
+ findFocusable($element).first().focus();
+
+ // ... and install new handler
+ focusin = function() {
+ if (!$.contains($element[0], document.activeElement) && $element[0] !== document.activeElement) {
+ // focus is outside of element, so pull in focus
+ findFocusable($element).first().focus();
+ }
+ };
+ $(document).on("focusin", focusin);
+
+ // listen for removal
+ $element.on("trapfocusremove", function() {
+ // turn off 'focusin' handler
+ $(document).off("focusin", focusin);
+
+ // ... restore old focus
+ if (oldActive) {
+ try {
+ oldActive.focus();
+ Wicket.Log.debug("trap-focus: restored focus to element ", oldActive);
+ } catch (error) {
+ Wicket.Log.error("trap-focus: error restoring focus. Attempted to set focus to element, but got an exception", oldActive, error);
+ }
+ }
+
+ // ... and re-install previous 'focusin' handler
+ focusin = previousfocusin;
+ $(document).on("focusin", focusin);
+ });
+ };
+
+}(jQuery, window, document, undefined));
\ No newline at end of file
diff --git a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/repeater/AjaxListPanel.html b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/repeater/AjaxListPanel.html
new file mode 100644
index 0000000..8f9aa2e
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/repeater/AjaxListPanel.html
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+ 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:panel xmlns:wicket="http://wicket.apache.org">
+ <div wicket:id="container">
+ <div wicket:id="repeater"></div>
+ </div>
+</wicket:panel>
\ No newline at end of file
diff --git a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/repeater/AjaxListPanel.java b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/repeater/AjaxListPanel.java
new file mode 100644
index 0000000..cfaa74c
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/repeater/AjaxListPanel.java
@@ -0,0 +1,114 @@
+/*
+ * 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.extensions.ajax.markup.html.repeater;
+
+import java.util.EmptyStackException;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.markup.repeater.RepeatingView;
+
+/**
+ * An panel for an <it>ajaxified</it> list of components.
+ * <p>
+ * Allows to append and delete components without an update of a whole container.
+ *
+ * @see #append(Component, AjaxRequestTarget)
+ * @see #delete(Component, AjaxRequestTarget)
+ *
+ * @author svenmeier
+ */
+public class AjaxListPanel extends Panel
+{
+
+ private static final long serialVersionUID = 1L;
+
+ private WebMarkupContainer container;
+
+ private RepeatingView repeater;
+
+ public AjaxListPanel(String id)
+ {
+ super(id);
+
+ this.container = new WebMarkupContainer("container");
+ this.container.setOutputMarkupId(true);
+ add(this.container);
+
+ this.repeater = new RepeatingView("repeater");
+ this.container.add(this.repeater);
+ }
+
+ /**
+ * Get an id for a new child to be appended.
+ *
+ * @return id
+ *
+ * @see #append(Component, AjaxRequestTarget)
+ */
+ public String newChildId() {
+ return repeater.newChildId();
+ }
+
+ /**
+ * Append a component.
+ *
+ * @param component
+ * the component
+ * @param target
+ * optional target
+ * @return the component
+ *
+ * @param T component type
+ */
+ public <T extends Component> T append(T component, AjaxRequestTarget target)
+ {
+ this.repeater.add(component);
+
+ if (target != null)
+ {
+ target.prependJavaScript(String.format("Wicket.DOM.add(Wicket.DOM.get('%s'), '<div id=\"%s\" />');",
+ container.getMarkupId(), component.getMarkupId()));
+ target.add(component);
+ }
+
+ return component;
+ }
+
+ /**
+ * Delete a component.
+ *
+ * @param target
+ * optional target
+ * @return the component
+ * @throws EmptyStackException if empty
+ *
+ * @param T component type
+ */
+ public <T extends Component> T delete(T component, AjaxRequestTarget target) {
+
+ this.repeater.remove(component);
+ if (target != null)
+ {
+ target.appendJavaScript(String.format("Wicket.DOM.remove(Wicket.DOM.get('%s'));", component.getMarkupId()));
+ }
+
+ return (T)component;
+ }
+}
\ No newline at end of file