001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.extensions.ajax.markup.html.modal; 018 019import org.apache.wicket.Component; 020import org.apache.wicket.WicketRuntimeException; 021import org.apache.wicket.ajax.AjaxEventBehavior; 022import org.apache.wicket.ajax.AjaxRequestTarget; 023import org.apache.wicket.ajax.attributes.AjaxCallListener; 024import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; 025import org.apache.wicket.ajax.attributes.AjaxRequestAttributes.EventPropagation; 026import org.apache.wicket.extensions.ajax.markup.html.modal.theme.DefaultTheme; 027import org.apache.wicket.markup.html.WebMarkupContainer; 028import org.apache.wicket.markup.html.panel.Panel; 029 030/** 031 * Presents a modal dialog to the user. See {@link #open(Component, AjaxRequestTarget)} and 032 * {@link #close(AjaxRequestTarget)} methods. 033 * <p> 034 * Unlike the deprecated {@link ModalWindow} this component offers no UI controls, you should use 035 * any components as you need in the content of this dialog. 036 * <p> 037 * Note: This component does not provide any styling by itself, so you have can add a 038 * {@link DefaultTheme} to this component if aren't styling these CSS classes by yourself: 039 * <dl> 040 * <dt>modal-dialog-overlay</dt> 041 * <dd>the wrapper around the actual dialog, usually used to overlay the rest of the document</dd> 042 * <dt>modal-dialog</dt> 043 * <dd>the actual dialog</dd> 044 * <dt>modal-dialog-content</dt> 045 * <dd>any additional styling for the content of this dialog</dd> 046 * </dl> 047 * 048 * @author Igor Vaynberg (ivaynberg) 049 * @author svenmeier 050 */ 051public class ModalDialog extends Panel 052{ 053 054 private static final long serialVersionUID = 1L; 055 056 private static final String OVERLAY_ID = "overlay"; 057 058 private static final String DIALOG_ID = "dialog"; 059 060 /** 061 * The id for the content of this dialoh. 062 * 063 * @see #setContent(Component) 064 * @see #open(Component, AjaxRequestTarget) 065 */ 066 public static final String CONTENT_ID = "content"; 067 068 private final WebMarkupContainer overlay; 069 070 private final WebMarkupContainer dialog; 071 072 private boolean removeContentOnClose; 073 074 public ModalDialog(String id) 075 { 076 super(id); 077 078 setOutputMarkupId(true); 079 080 overlay = newOverlay(OVERLAY_ID); 081 overlay.setVisible(false); 082 add(overlay); 083 084 dialog = newDialog(DIALOG_ID); 085 overlay.add(dialog); 086 } 087 088 /** 089 * Factory method for the overlay markup around the dialog. 090 * 091 * @param overlayId 092 * id 093 * @return overlay 094 */ 095 protected WebMarkupContainer newOverlay(String overlayId) 096 { 097 return new WebMarkupContainer(overlayId); 098 } 099 100 /** 101 * Factory method for the dialog markup around the content. 102 * 103 * @param dialogId 104 * id 105 * @return overlay 106 */ 107 protected WebMarkupContainer newDialog(String dialogId) 108 { 109 return new WebMarkupContainer(dialogId); 110 } 111 112 /** 113 * Set a content. 114 * 115 * @param content 116 * 117 * @see #open(AjaxRequestTarget) 118 */ 119 public void setContent(Component content) 120 { 121 if (!CONTENT_ID.equals(content.getId())) 122 { 123 throw new IllegalArgumentException( 124 "Content must have wicket id set to ModalDialog.CONTENT_ID"); 125 } 126 127 dialog.addOrReplace(content); 128 129 removeContentOnClose = false; 130 } 131 132 /** 133 * Open the dialog with a content. 134 * <p> 135 * The content will be removed on close of the dialog. 136 * 137 * @param content 138 * the content 139 * @param target 140 * an optional Ajax target 141 * @return this 142 * 143 * @see #setContent(Component) 144 * @see #close(AjaxRequestTarget) 145 */ 146 public ModalDialog open(Component content, AjaxRequestTarget target) 147 { 148 setContent(content); 149 removeContentOnClose = true; 150 151 overlay.setVisible(true); 152 153 if (target != null) 154 { 155 target.add(this); 156 } 157 158 return this; 159 } 160 161 /** 162 * Open the dialog. 163 * 164 * @param target 165 * an optional Ajax target 166 * @return this 167 * 168 * @see #setContent(Component) 169 */ 170 public ModalDialog open(AjaxRequestTarget target) 171 { 172 if (overlay.size() == 0) 173 { 174 throw new WicketRuntimeException(String.format("ModalDialog with id '%s' has no content set!", getId())); 175 } 176 177 overlay.setVisible(true); 178 179 if (target != null) 180 { 181 target.add(this); 182 } 183 184 return this; 185 } 186 187 /** 188 * Is this dialog open. 189 * 190 * @return <code>true</code> if open 191 */ 192 public boolean isOpen() 193 { 194 return overlay.isVisible(); 195 } 196 197 /** 198 * Close this dialog. 199 * <p> 200 * If opened via {@link #open(Component, AjaxRequestTarget)}, the content is removed from the 201 * component tree 202 * 203 * @param target 204 * an optional Ajax target 205 * @return this 206 * 207 * @see #open(Component, AjaxRequestTarget) 208 */ 209 public ModalDialog close(AjaxRequestTarget target) 210 { 211 overlay.setVisible(false); 212 if (removeContentOnClose) 213 { 214 dialog.removeAll(); 215 } 216 217 if (target != null) 218 { 219 target.add(this); 220 } 221 222 return this; 223 } 224 225 /** 226 * Close this dialog on press of escape key. 227 * 228 * @return this 229 */ 230 public ModalDialog closeOnEscape() 231 { 232 overlay.add(new CloseBehavior("keydown") 233 { 234 protected CharSequence getPrecondition() 235 { 236 return "return Wicket.Event.keyCode(attrs.event) == 27"; 237 } 238 }); 239 return this; 240 } 241 242 /** 243 * Close this dialog on click outside. 244 * 245 * @return this 246 */ 247 public ModalDialog closeOnClick() 248 { 249 overlay.add(new CloseBehavior("click") 250 { 251 protected CharSequence getPrecondition() 252 { 253 return String.format("return attrs.event.target.id === '%s';", overlay.getMarkupId()); 254 } 255 }); 256 return this; 257 } 258 259 /** 260 * Convenience method to trap focus inside the overlay. 261 * 262 * @see TrapFocusBehavior 263 * 264 * @return this 265 */ 266 public ModalDialog trapFocus() 267 { 268 overlay.add(new TrapFocusBehavior()); 269 270 return this; 271 } 272 273 private abstract class CloseBehavior extends AjaxEventBehavior 274 { 275 private CloseBehavior(String event) 276 { 277 super(event); 278 } 279 280 @Override 281 protected void updateAjaxAttributes(AjaxRequestAttributes attributes) 282 { 283 super.updateAjaxAttributes(attributes); 284 285 // has to stop immediately to prevent an enclosing dialog to close too 286 attributes.setEventPropagation(EventPropagation.STOP_IMMEDIATE); 287 288 attributes.getAjaxCallListeners().add(new AjaxCallListener() 289 { 290 @Override 291 public CharSequence getPrecondition(Component component) 292 { 293 return CloseBehavior.this.getPrecondition(); 294 } 295 }); 296 } 297 298 protected CharSequence getPrecondition() 299 { 300 return ""; 301 } 302 303 @Override 304 protected void onEvent(AjaxRequestTarget target) 305 { 306 close(target); 307 } 308 } 309}