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 * Note: This component does not provide any styling by itself, so you have can add a 035 * {@link DefaultTheme} to this component if aren't styling these CSS classes by yourself: 036 * <dl> 037 * <dt>modal-dialog-overlay</dt> 038 * <dd>the wrapper around the actual dialog, usually used to overlay the rest of the document</dd> 039 * <dt>modal-dialog</dt> 040 * <dd>the actual dialog</dd> 041 * <dt>modal-dialog-content</dt> 042 * <dd>any additional styling for the content of this dialog</dd> 043 * </dl> 044 * 045 * @author Igor Vaynberg (ivaynberg) 046 * @author svenmeier 047 */ 048public class ModalDialog extends Panel 049{ 050 051 private static final long serialVersionUID = 1L; 052 053 private static final String OVERLAY_ID = "overlay"; 054 055 private static final String DIALOG_ID = "dialog"; 056 057 /** 058 * The id for the content of this dialoh. 059 * 060 * @see #setContent(Component) 061 * @see #open(Component, AjaxRequestTarget) 062 */ 063 public static final String CONTENT_ID = "content"; 064 065 private final WebMarkupContainer overlay; 066 067 private final WebMarkupContainer dialog; 068 069 private boolean removeContentOnClose; 070 071 public ModalDialog(String id) 072 { 073 super(id); 074 075 setOutputMarkupId(true); 076 077 overlay = newOverlay(OVERLAY_ID); 078 overlay.setVisible(false); 079 add(overlay); 080 081 dialog = newDialog(DIALOG_ID); 082 overlay.add(dialog); 083 } 084 085 /** 086 * Factory method for the overlay markup around the dialog. 087 * 088 * @param overlayId 089 * id 090 * @return overlay 091 */ 092 protected WebMarkupContainer newOverlay(String overlayId) 093 { 094 return new WebMarkupContainer(overlayId); 095 } 096 097 /** 098 * Factory method for the dialog markup around the content. 099 * 100 * @param dialogId 101 * id 102 * @return overlay 103 */ 104 protected WebMarkupContainer newDialog(String dialogId) 105 { 106 return new WebMarkupContainer(dialogId); 107 } 108 109 /** 110 * Set a content. 111 * 112 * @param content 113 * @see #open(AjaxRequestTarget) 114 */ 115 public void setContent(Component content) 116 { 117 if (!CONTENT_ID.equals(content.getId())) 118 { 119 throw new IllegalArgumentException( 120 "Content must have wicket id set to ModalDialog.CONTENT_ID"); 121 } 122 123 dialog.addOrReplace(content); 124 125 removeContentOnClose = false; 126 } 127 128 /** 129 * Open the dialog with a content. 130 * <p> 131 * The content will be removed on close of the dialog. 132 * 133 * @param content 134 * the content 135 * @param target 136 * an optional Ajax target 137 * @return this 138 * @see #setContent(Component) 139 * @see #close(AjaxRequestTarget) 140 */ 141 public ModalDialog open(Component content, AjaxRequestTarget target) 142 { 143 setContent(content); 144 removeContentOnClose = true; 145 146 overlay.setVisible(true); 147 148 if (target != null) 149 { 150 target.add(this); 151 } 152 153 return this; 154 } 155 156 /** 157 * Open the dialog. 158 * 159 * @param target 160 * an optional Ajax target 161 * @return this 162 * @see #setContent(Component) 163 */ 164 public ModalDialog open(AjaxRequestTarget target) 165 { 166 if (overlay.size() == 0) 167 { 168 throw new WicketRuntimeException( 169 String.format("ModalDialog with id '%s' has no content set!", getId())); 170 } 171 172 overlay.setVisible(true); 173 174 if (target != null) 175 { 176 target.add(this); 177 } 178 179 return this; 180 } 181 182 /** 183 * Is this dialog open. 184 * 185 * @return <code>true</code> if open 186 */ 187 public boolean isOpen() 188 { 189 return overlay.isVisible(); 190 } 191 192 /** 193 * Close this dialog. 194 * <p> 195 * If opened via {@link #open(Component, AjaxRequestTarget)}, the content is removed from the 196 * component tree 197 * 198 * @param target 199 * an optional Ajax target 200 * @return this 201 * @see #open(Component, AjaxRequestTarget) 202 */ 203 public ModalDialog close(AjaxRequestTarget target) 204 { 205 overlay.setVisible(false); 206 if (removeContentOnClose) 207 { 208 dialog.removeAll(); 209 } 210 211 if (target != null) 212 { 213 target.add(this); 214 } 215 216 return this; 217 } 218 219 /** 220 * Close this dialog on press of escape key. 221 * 222 * @return this 223 */ 224 public ModalDialog closeOnEscape() 225 { 226 overlay.add(new CloseBehavior("keydown") 227 { 228 protected CharSequence getPrecondition() 229 { 230 return "return Wicket.Event.keyCode(attrs.event) == 27"; 231 } 232 }); 233 return this; 234 } 235 236 /** 237 * Close this dialog on click outside. 238 * 239 * @return this 240 */ 241 public ModalDialog closeOnClick() 242 { 243 overlay.add(new CloseBehavior("click") 244 { 245 protected CharSequence getPrecondition() 246 { 247 return String.format("return attrs.event.target.id === '%s';", 248 overlay.getMarkupId()); 249 } 250 }); 251 return this; 252 } 253 254 /** 255 * Convenience method to trap focus inside the overlay. 256 * 257 * @return this 258 * @see TrapFocusBehavior 259 */ 260 public ModalDialog trapFocus() 261 { 262 overlay.add(new TrapFocusBehavior()); 263 264 return this; 265 } 266 267 /** 268 * can be overridden to change the {@link AjaxRequestAttributes} of the default 269 * {@link CloseBehavior} executed on close. For example to change the Ajax Channel. 270 * 271 * @param attributes 272 * the {@link AjaxRequestAttributes} of the default {@link CloseBehavior} 273 */ 274 protected void postProcessCloseBehaviorAjaxAttributes(AjaxRequestAttributes attributes) 275 { 276 // no op 277 } 278 279 private abstract class CloseBehavior extends AjaxEventBehavior 280 { 281 private CloseBehavior(String event) 282 { 283 super(event); 284 } 285 286 @Override 287 protected void updateAjaxAttributes(AjaxRequestAttributes attributes) 288 { 289 super.updateAjaxAttributes(attributes); 290 291 // has to stop immediately to prevent an enclosing dialog to close too 292 attributes.setEventPropagation(EventPropagation.STOP_IMMEDIATE); 293 294 attributes.getAjaxCallListeners().add(new AjaxCallListener() 295 { 296 @Override 297 public CharSequence getPrecondition(Component component) 298 { 299 return CloseBehavior.this.getPrecondition(); 300 } 301 }); 302 303 postProcessCloseBehaviorAjaxAttributes(attributes); 304 } 305 306 protected CharSequence getPrecondition() 307 { 308 return ""; 309 } 310 311 @Override 312 protected void onEvent(AjaxRequestTarget target) 313 { 314 close(target); 315 } 316 } 317}