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.Page; 021import org.apache.wicket.WicketRuntimeException; 022import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior; 023import org.apache.wicket.ajax.AjaxRequestTarget; 024import org.apache.wicket.ajax.json.JSONFunction; 025import org.apache.wicket.core.request.handler.IPartialPageRequestHandler; 026import org.apache.wicket.core.request.handler.PageProvider; 027import org.apache.wicket.core.request.handler.RenderPageRequestHandler; 028import org.apache.wicket.markup.ComponentTag; 029import org.apache.wicket.markup.head.CssHeaderItem; 030import org.apache.wicket.markup.head.IHeaderResponse; 031import org.apache.wicket.markup.head.JavaScriptHeaderItem; 032import org.apache.wicket.markup.html.WebMarkupContainer; 033import org.apache.wicket.markup.html.panel.Panel; 034import org.apache.wicket.markup.repeater.AbstractRepeater; 035import org.apache.wicket.model.IModel; 036import org.apache.wicket.model.Model; 037import org.apache.wicket.request.IRequestHandler; 038import org.apache.wicket.request.cycle.RequestCycle; 039import org.apache.wicket.request.resource.CssResourceReference; 040import org.apache.wicket.request.resource.JavaScriptResourceReference; 041import org.apache.wicket.request.resource.ResourceReference; 042import org.apache.wicket.resource.CoreLibrariesContributor; 043import org.apache.wicket.util.io.IClusterable; 044import org.apache.wicket.util.lang.EnumeratedType; 045import org.apache.wicket.util.string.AppendingStringBuffer; 046 047import com.github.openjson.JSONObject; 048 049/** 050 * Modal window component. 051 * <p> 052 * Modal window is a draggable window (with either <div> when used with a Panel or <iframe> 053 * when used with a Page content) that prevent user from 054 * interacting the rest of page (using a mask) until the window is closed. 055 * <p> 056 * If you want this to work under IE, don't attach this component to a <span> tag, make sure 057 * you use a <div>. 058 * <p> 059 * The window is draggable and optionally resizable. The content can be either 060 * <ul> 061 * <li><b>a component</b> - you need to add the component to modal window (with id obtained using 062 * <code>{@link #getContentId()}</code>, or 063 * <li><b>a page</b> - you need to pass a <code>{@link PageCreator}</code> instance to a 064 * <code>{@link #setPageCreator(ModalWindow.PageCreator)}</code> method. 065 * </ul> 066 * In case the content is a component, it is not rendered until the window is shown (method 067 * <code>{@link #show(IPartialPageRequestHandler)})</code>. The window can be made 068 * visible from an ajax handler using 069 * <code>{@link #show(IPartialPageRequestHandler)}</code>. 070 * <p> 071 * To close the window there are multiple options. Static method 072 * <code>{@link #close(IPartialPageRequestHandler)}</code> can be used to close the 073 * window from a handler of ajax link inside the window. By default the close button in the upper 074 * right corner of the window closes it. This behavior can be altered using 075 * <code>{@link #setCloseButtonCallback(ModalWindow.CloseButtonCallback)}</code>. If you want to be 076 * notified when the window is closed (either using the close button or calling 077 * <code>{@link #close(IPartialPageRequestHandler)})</code>, you can use 078 * <code>{@link #setWindowClosedCallback(ModalWindow.WindowClosedCallback)}</code>. 079 * <p> 080 * Title is specified using {@link #setTitle(String)}. Pass <code>true</code> to 081 * <code>{@link #setEscapeModelStrings(boolean)}</code> to use unencoded markup in the title. 082 * <br> 083 * If the content is a page (iframe), the title can remain unset, in that case title from the page 084 * inside window will be shown. 085 * <p> 086 * There are several options to specify the visual properties of the window. In all methods where 087 * size is expected, width refers to width of entire window (including frame), height refers to the 088 * height of window content (without frame). 089 * <p> 090 * <ul> 091 * <li><code>{@link #setResizable(boolean)}</code> specifies, whether the window can be resized. 092 * <li><code>{@link #setInitialWidth(int)}</code> and <code>{@link #setInitialHeight(int)}</code> 093 * specify the initial width and height of window. If the window is resizable, the unit of these 094 * dimensions is always "px". If the window is not resizable, the unit can be specified using 095 * <code>{@link #setWidthUnit(String)}</code> and <code>{@link #setHeightUnit(String)}</code>. If 096 * the window is not resizable and the content is a component (not a page), the initial height value 097 * can be ignored and the actual height can be determined from the height of the content. To enable 098 * this behavior use <code>{@link #setUseInitialHeight(boolean)}</code>. 099 * <li>The window position (and size if the window is resizable) can be stored in a cookie, so that 100 * it is preserved when window is close. The name of the cookie is specified via 101 * <code>{@link #setCookieName(String)}</code>. If the name is <code>null</code>, position is not 102 * stored (initial width and height are always used). Default cookie name is null (position is not 103 * stored). 104 * <li><code>{@link #setMinimalWidth(int)}</code> and <code>{@link #setMinimalHeight(int)}</code> 105 * set the minimal dimensions of resizable window. 106 * <li><code>{@link #setAutoSize(boolean)}</code> sets whether window size will be automatically 107 * adjusted on opening to fit content's width and height. Default is false.<span 108 * style="text-decoration: underline"> Doesn't work on IE 6.</span></li> 109 * <li>Modal window can chose between two colors of frame. 110 * <code>{@link #setCssClassName(String)}</code> sets the dialog css class, possible values are 111 * <code>{@link #CSS_CLASS_BLUE}</code> for blue frame and <code>{@link #CSS_CLASS_GRAY}</code> for 112 * gray frame. 113 * <li>Mask (element that prevents user from interacting the rest of the page) can be either 114 * transparent or semitransparent. <code>{@link #setMaskType(ModalWindow.MaskType)}</code> alters 115 * this. 116 * </ul> 117 * Also it is recommended to put the modal window component in markup before any component (i.e. 118 * AjaxLink or AjaxButton) that shows it. 119 * <p> 120 * If you want to use form in modal window component make sure that you put the modal window itself 121 * in another form (nesting forms is legal in Wicket) and that the form on modal window is submitted 122 * before the window get closed. 123 * 124 * @author Matej Knopp 125 * 126 * @deprecated use {@link ModalDialog} instead 127 */ 128public class ModalWindow extends Panel 129{ 130 private static final long serialVersionUID = 1L; 131 132 /** CSS class for window with blue border. */ 133 public final static String CSS_CLASS_BLUE = "w_blue"; 134 135 /** CSS class for window with gray border. */ 136 public final static String CSS_CLASS_GRAY = "w_silver"; 137 138 private static final ResourceReference JAVASCRIPT = new JavaScriptResourceReference( 139 ModalWindow.class, "res/modal.js"); 140 141 private static final ResourceReference CSS = new CssResourceReference(ModalWindow.class, 142 "res/modal.css"); 143 144 /** the default id of the content component */ 145 public static final String CONTENT_ID = "content"; 146 147 /** True while the ModalWindows is showing */ 148 private boolean shown = false; 149 150 /** empty container - used when no component is added */ 151 private WebMarkupContainer empty; 152 153 private int minimalWidth = 200; 154 private int minimalHeight = 200; 155 private String cssClassName = CSS_CLASS_BLUE; 156 private int initialWidth = 600; 157 private int initialHeight = 400; 158 private boolean useInitialHeight = true; 159 private boolean resizable = true; 160 private String widthUnit = "px"; 161 private String heightUnit = "px"; 162 private String cookieName; 163 private IModel<String> title = null; 164 private MaskType maskType = MaskType.SEMI_TRANSPARENT; 165 private boolean autoSize = false; 166 private boolean unloadConfirmation = true; 167 168 private PageCreator pageCreator = null; 169 private CloseButtonCallback closeButtonCallback = null; 170 private WindowClosedCallback windowClosedCallback = null; 171 172 /** 173 * Interface for lazy page creation. The advantage of creating page using this interface over 174 * just passing a page instance is that page created in <code>{@link #createPage()}</code> will 175 * have the pagemap automatically set to the pagemap specified for 176 * <code>{@link ModalWindow}</code>. 177 * 178 * @author Matej Knopp 179 */ 180 public interface PageCreator extends IClusterable 181 { 182 /** 183 * Creates a new instance of content page. 184 * 185 * @return new page instance 186 */ 187 Page createPage(); 188 } 189 190 /** 191 * Callback for close button that contains a method that is invoked after the button has been 192 * clicked. If no callback instance is specified using 193 * <code>{@link ModalWindow#setCloseButtonCallback(ModalWindow.CloseButtonCallback)}</code>, no 194 * ajax request will be fired. Clicking the button will just close the window. 195 * 196 * @author Matej Knopp 197 */ 198 public interface CloseButtonCallback extends IClusterable 199 { 200 /** 201 * Methods invoked after the button has been clicked. The invocation is done using an ajax 202 * call, so <code>{@link org.apache.wicket.ajax.AjaxRequestTarget}</code> instance is 203 * available. 204 * 205 * @param target 206 * <code>{@link org.apache.wicket.ajax.AjaxRequestTarget}</code> instance bound 207 * with the ajax request. 208 * 209 * @return True if the window can be closed (will close the window), false otherwise 210 */ 211 boolean onCloseButtonClicked(AjaxRequestTarget target); 212 } 213 214 /** 215 * Callback called after the window has been closed. If no callback instance is specified using 216 * {@link ModalWindow#setWindowClosedCallback(ModalWindow.WindowClosedCallback)}, no ajax 217 * request will be fired. 218 * 219 * @author Matej Knopp 220 */ 221 public interface WindowClosedCallback extends IClusterable 222 { 223 /** 224 * Called after the window has been closed. 225 * 226 * @param target 227 * <code>{@link org.apache.wicket.ajax.AjaxRequestTarget}</code> instance bound 228 * with the ajax request. 229 */ 230 void onClose(AjaxRequestTarget target); 231 } 232 233 /** 234 * Creates a new modal window component. 235 * 236 * @param id 237 * Id of component 238 */ 239 public ModalWindow(final String id) 240 { 241 super(id); 242 init(); 243 } 244 245 /** 246 * Creates a new modal window component. 247 * 248 * @param id 249 * Id of component 250 * @param model 251 * Model 252 */ 253 public ModalWindow(final String id, final IModel<?> model) 254 { 255 super(id, model); 256 init(); 257 } 258 259 /** 260 * Initialize 261 */ 262 private void init() 263 { 264 setVersioned(false); 265 cookieName = null; 266 267 add(empty = new WebMarkupContainer(getContentId())); 268 269 add(newCloseButtonBehavior()); 270 add(new WindowClosedBehavior()); 271 272 // install a default callback that will force 273 // WindowClosedBehavior to be executed 274 setWindowClosedCallback((WindowClosedCallback) target -> { 275 // noop 276 }); 277 278 } 279 280 @Override 281 public void renderHead(final IHeaderResponse response) 282 { 283 super.renderHead(response); 284 285 CoreLibrariesContributor.contributeAjax(getApplication(), response); 286 response.render(JavaScriptHeaderItem.forReference(JAVASCRIPT)); 287 288 ResourceReference cssResource = newCssResource(); 289 if (cssResource != null) 290 { 291 response.render(CssHeaderItem.forReference(cssResource)); 292 } 293 } 294 295 /** 296 * Allows to override CSS contribution. Returning null means the CSS will be contributed via 297 * other sources, e.g. a global CSS resource. 298 * 299 * @return The CSS resource reference or null if CSS is contributed via other means. 300 * @see #setCssClassName(String) 301 */ 302 protected ResourceReference newCssResource() 303 { 304 return CSS; 305 } 306 307 /** 308 * Is this window currently showing. 309 * 310 * @return the shown 311 */ 312 public boolean isShown() 313 { 314 return shown; 315 } 316 317 318 /** 319 * Sets the <code>{@link PageCreator}</code> instance. The instance is only used when no custom 320 * component has been added to the dialog. 321 * 322 * @param creator 323 * <code>{@link PageCreator}</code> instance 324 * @return this 325 */ 326 public ModalWindow setPageCreator(final PageCreator creator) 327 { 328 setContent(empty); 329 pageCreator = creator; 330 return this; 331 } 332 333 /** 334 * Sets the <code>{@link CloseButtonCallback}</code> instance. 335 * 336 * @param callback 337 * Callback instance 338 * @return this 339 */ 340 public ModalWindow setCloseButtonCallback(final CloseButtonCallback callback) 341 { 342 closeButtonCallback = callback; 343 return this; 344 } 345 346 /** 347 * Sets the <code>@{link {@link WindowClosedCallback}</code> instance. 348 * 349 * @param callback 350 * Callback instance 351 * @return this 352 */ 353 public ModalWindow setWindowClosedCallback(final WindowClosedCallback callback) 354 { 355 windowClosedCallback = callback; 356 return this; 357 } 358 359 /** 360 * Shows the modal window. 361 * 362 * @param target 363 * Request target associated with current ajax request. 364 */ 365 public void show(final IPartialPageRequestHandler target) 366 { 367 if (shown == false) 368 { 369 getContent().setVisible(true); 370 target.add(this); 371 target.appendJavaScript(getWindowOpenJavaScript()); 372 shown = true; 373 } 374 } 375 376 /** 377 * Hides the modal window. This can be called from within the modal window, however, the modal 378 * window must have configured WindowClosedCallback. Otherwise use the 379 * {@link #close(IPartialPageRequestHandler)} method. 380 * 381 * @param target 382 * Request target associated with current ajax request. 383 */ 384 public static void closeCurrent(final IPartialPageRequestHandler target) 385 { 386 target.appendJavaScript(getCloseJavacriptInternal()); 387 } 388 389 /** 390 * Closes the modal window. 391 * 392 * @param target 393 * Request target associated with current ajax request. 394 */ 395 public void close(final IPartialPageRequestHandler target) 396 { 397 getContent().setVisible(false); 398 if (isCustomComponent()) 399 { 400 target.add(getContent()); 401 } 402 target.appendJavaScript(getCloseJavacript()); 403 shown = false; 404 } 405 406 /** 407 * Method that allows alternate script for showing the window. 408 * 409 * @return the script that actually shows the window. 410 */ 411 protected CharSequence getShowJavaScript() 412 { 413 return "window.setTimeout(function(){\n" + " Wicket.Window.create(settings).show();\n" 414 + "}, 0);\n"; 415 } 416 417 private static String getCloseJavacriptInternal() 418 { 419 return "var win;\n" // 420 + "try {\n" + " win = window.parent.Wicket.Window;\n" 421 + "} catch (ignore) {\n" 422 + "}\n" 423 + "if (typeof(win) == \"undefined\" || typeof(win.current) == \"undefined\") {\n" 424 + " try {\n" + " win = window.Wicket.Window;\n" 425 + " } catch (ignore) {\n" 426 + " }\n" 427 + "}\n" 428 + "if (win && win.current) {\n" 429 + " var close = function(w) { w.setTimeout(function() {\n" 430 + " win.current.close();\n" 431 + " }, 0); };\n" 432 + " try { close(window.parent); } catch (ignore) { close(window); }\n" + "}"; 433 } 434 435 /** 436 * Method that allows alternate script for closing the window. 437 * 438 * @return the script that actually closes the window. 439 */ 440 protected String getCloseJavacript() 441 { 442 return getCloseJavacriptInternal(); 443 } 444 445 /** 446 * Returns the id of content component. 447 * 448 * <pre> 449 * ModalWindow window = new ModalWindow(parent, "window"); 450 * new MyPanel(window, window.getContentId()); 451 * </pre> 452 * 453 * @return Id of content component. 454 */ 455 public String getContentId() 456 { 457 return CONTENT_ID; 458 } 459 460 /** 461 * Sets the minimal width of window. This value is only used if the window is resizable. The 462 * width is specified in pixels and it is the width of entire window (including frame). 463 * 464 * @param minimalWidth 465 * Minimal window width. 466 * @return this 467 */ 468 public ModalWindow setMinimalWidth(final int minimalWidth) 469 { 470 this.minimalWidth = minimalWidth; 471 return this; 472 } 473 474 /** 475 * Returns the minimal width of window (in pixels). 476 * 477 * @return Minimal width of window 478 */ 479 public int getMinimalWidth() 480 { 481 return minimalWidth; 482 } 483 484 /** 485 * Sets the minimal height of window. This value is only used if window is resizable. The height 486 * is specified in pixels and it is the height of window content (without frame). 487 * 488 * @param minimalHeight 489 * Minimal height 490 * @return this 491 */ 492 public ModalWindow setMinimalHeight(final int minimalHeight) 493 { 494 this.minimalHeight = minimalHeight; 495 return this; 496 } 497 498 /** 499 * Returns the minimal height of window (in pixels). 500 * 501 * @return Minimal height of window 502 */ 503 public int getMinimalHeight() 504 { 505 return minimalHeight; 506 } 507 508 /** 509 * Sets the CSS class name for this window. This class affects the look of window frame. 510 * Possible values (if you don't make your style sheet) are <code>{@link #CSS_CLASS_BLUE}</code> 511 * and <code>{@link #CSS_CLASS_GRAY}</code>. 512 * 513 * @param cssClassName 514 * @return this 515 * @see #newCssResource() 516 */ 517 public ModalWindow setCssClassName(final String cssClassName) 518 { 519 this.cssClassName = cssClassName; 520 return this; 521 } 522 523 /** 524 * Returns the CSS class name for this window. 525 * 526 * @return CSS class name 527 */ 528 public String getCssClassName() 529 { 530 return cssClassName; 531 } 532 533 /** 534 * Sets the initial width of the window. The width refers to the width of entire window 535 * (including frame). If the window is resizable, the width unit is always "px". If the window 536 * is not resizable, the unit can be specified using {@link #setWidthUnit(String)}. If cookie 537 * name is set and window is resizable, the initial width may be ignored in favor of width 538 * stored in cookie. 539 * 540 * @param initialWidth 541 * Initial width of the window 542 * @return this 543 */ 544 public ModalWindow setInitialWidth(final int initialWidth) 545 { 546 this.initialWidth = initialWidth; 547 return this; 548 } 549 550 /** 551 * Returns the initial width of the window. 552 * 553 * @return Initial height of the window 554 */ 555 public int getInitialWidth() 556 { 557 return initialWidth; 558 } 559 560 /** 561 * Sets the initial height of the window. The height refers to the height of window content 562 * (without frame). If the window is resizable, the height unit is always "px". If the window is 563 * not resizable, the unit can be specified using {@link #setHeightUnit(String)}. If cookie name 564 * is set and window is resizable, the initial height may be ignored in favor of height stored 565 * in cookie. 566 * 567 * @param initialHeight 568 * Initial height of the window 569 * @return this 570 */ 571 public ModalWindow setInitialHeight(final int initialHeight) 572 { 573 this.initialHeight = initialHeight; 574 return this; 575 } 576 577 /** 578 * Returns the initial height of the window. 579 * 580 * @return Initial height of the window 581 */ 582 public int getInitialHeight() 583 { 584 return initialHeight; 585 } 586 587 /** 588 * Sets whether to use initial height or preserve the real content height. This can only be used 589 * if the content is a component (not a page) and the window is not resizable. 590 * 591 * @param useInitialHeight 592 * Whether to use initial height instead of preserving content height instead of 593 * using initial height 594 * @return this 595 */ 596 public ModalWindow setUseInitialHeight(final boolean useInitialHeight) 597 { 598 this.useInitialHeight = useInitialHeight; 599 return this; 600 } 601 602 /** 603 * Returns true if the initial height should be used (in favor of preserving real content 604 * height). 605 * 606 * @return True if initial height should be used, false is real content height should be 607 * preserved (valid only if the window is not resizable and the content is a component 608 * (not a page) 609 */ 610 public boolean isUseInitialHeight() 611 { 612 return useInitialHeight; 613 } 614 615 /** 616 * Sets whether the user will be able to resize the window. 617 * 618 * @param resizable 619 * Whether the window is resizable 620 * @return this 621 */ 622 public ModalWindow setResizable(final boolean resizable) 623 { 624 this.resizable = resizable; 625 return this; 626 } 627 628 /** 629 * Returns whether the window is resizable. 630 * 631 * @return True if the window is resizable, false otherwise 632 */ 633 public boolean isResizable() 634 { 635 return resizable; 636 } 637 638 /** 639 * Sets a flag whether to ask the user before leaving the page. 640 * 641 * @param unloadConfirmation 642 * a flag whether to ask the user before leaving the page 643 * @return {@code this} instance, for chaining 644 */ 645 public ModalWindow showUnloadConfirmation(final boolean unloadConfirmation) 646 { 647 this.unloadConfirmation = unloadConfirmation; 648 return this; 649 } 650 651 /** 652 * Returns whether the user should be asked before leaving the page. 653 * 654 * @return {@code true} if the user should be asked if the last action causes leaving the page, 655 * {@code false} otherwise 656 */ 657 public boolean showUnloadConfirmation() 658 { 659 return unloadConfirmation; 660 } 661 662 /** 663 * Sets the CSS unit used for initial window width. This is only applicable when the window is 664 * not resizable. 665 * 666 * @param widthUnit 667 * CSS unit for initial window width. 668 * @return this 669 */ 670 public ModalWindow setWidthUnit(final String widthUnit) 671 { 672 this.widthUnit = widthUnit; 673 return this; 674 } 675 676 /** 677 * Returns the CSS unit for initial window width. 678 * 679 * @return CSS unit for initial window width. 680 */ 681 public String getWidthUnit() 682 { 683 return widthUnit; 684 } 685 686 /** 687 * Sets the CSS unit used for initial window height. This is only applicable when the window is 688 * not resizable. 689 * 690 * @param heightUnit 691 * CSS unit for initial window height. 692 * @return this 693 */ 694 public ModalWindow setHeightUnit(final String heightUnit) 695 { 696 this.heightUnit = heightUnit; 697 return this; 698 } 699 700 /** 701 * Retrns the CSS unit for initial window height. 702 * 703 * @return CSS unit for initial window height. 704 */ 705 public String getHeightUnit() 706 { 707 return heightUnit; 708 } 709 710 /** 711 * Sets the name of the cookie that is used to remember window position (and size if the window 712 * is resizable). 713 * 714 * @param cookieName 715 * Name of the cookie 716 * @return this 717 */ 718 public ModalWindow setCookieName(final String cookieName) 719 { 720 if ((cookieName != null) && (cookieName.contains(",") || cookieName.contains("|"))) 721 { 722 throw new IllegalArgumentException("Cookie name may not contain ',' or '|' characters."); 723 } 724 this.cookieName = cookieName; 725 return this; 726 } 727 728 /** 729 * Returns the name of cookie that is used to remember window position (and size if the window 730 * is resizable). 731 * 732 * @return Name of the cookie 733 */ 734 public String getCookieName() 735 { 736 return cookieName; 737 } 738 739 /** 740 * Sets the title of window. If the window is a page, title can be <code>null</code>. In that 741 * case it will display the title document inside the window. 742 * 743 * @param title 744 * Title of the window 745 * @return this 746 */ 747 public ModalWindow setTitle(final String title) 748 { 749 this.title = new Model<>(title); 750 return this; 751 } 752 753 /** 754 * Sets the title of window. If the window is a page, title can be <code>null</code>. In that 755 * case it will display the title document inside the window. 756 * 757 * @param title 758 * Title of the window 759 * @return this 760 */ 761 public ModalWindow setTitle(IModel<String> title) 762 { 763 title = wrap(title); 764 this.title = title; 765 return this; 766 } 767 768 /** 769 * Returns the title of the window. 770 * 771 * @return Title of the window 772 */ 773 public IModel<String> getTitle() 774 { 775 return title; 776 } 777 778 /** 779 * Mask is the element behind the window, that prevents user from interacting the rest of page. 780 * Mask can be either 781 * <ul> 782 * <li><code>{@link #TRANSPARENT}</code> - the mask is invisible 783 * <li><code>{@link #SEMI_TRANSPARENT}</code> - the mask is black with small opacity (10%) 784 * </ul> 785 * 786 * @author Matej Knopp 787 */ 788 public static final class MaskType extends EnumeratedType 789 { 790 private static final long serialVersionUID = 1L; 791 792 /** Transparent mask (not visible). */ 793 public static final MaskType TRANSPARENT = new MaskType("TRANSPARENT"); 794 795 /** Visible mask (black with low opacity). */ 796 public static final MaskType SEMI_TRANSPARENT = new MaskType("SEMI_TRANSPARENT"); 797 798 /** 799 * Constructor. 800 * 801 * @param name 802 */ 803 public MaskType(final String name) 804 { 805 super(name); 806 } 807 } 808 809 /** 810 * Sets the mask type of the window. 811 * 812 * @param mask 813 * The mask type 814 * @return this 815 */ 816 public ModalWindow setMaskType(final MaskType mask) 817 { 818 maskType = mask; 819 return this; 820 } 821 822 /** 823 * Returns the mask type of the window 824 * 825 * @return The mask type 826 */ 827 public MaskType getMaskType() 828 { 829 return maskType; 830 } 831 832 /** 833 * Creates the page. 834 * 835 * @return Page instance or null if page couldn't be created. 836 */ 837 private Page createPage() 838 { 839 if (pageCreator == null) 840 { 841 return null; 842 } 843 else 844 { 845 return pageCreator.createPage(); 846 } 847 } 848 849 /** 850 * @see org.apache.wicket.Component#onBeforeRender() 851 */ 852 @Override 853 protected void onBeforeRender() 854 { 855 shown = makeContentVisible(); 856 857 getContent().setOutputMarkupId(true); 858 getContent().setVisible(shown); 859 860 super.onBeforeRender(); 861 } 862 863 /** 864 * You may subclass this method in case you don't want to show up the window on normal page 865 * refresh. 866 * 867 * @return true, if the window shall be shown 868 */ 869 protected boolean makeContentVisible() 870 { 871 // if user is refreshing whole page, the window will not be shown 872 if (getWebRequest().isAjax() == false) 873 { 874 return false; 875 } 876 else 877 { 878 return shown; 879 } 880 } 881 882 /** 883 * @see org.apache.wicket.markup.html.panel.Panel#onComponentTag(org.apache.wicket.markup.ComponentTag) 884 */ 885 @Override 886 protected void onComponentTag(final ComponentTag tag) 887 { 888 super.onComponentTag(tag); 889 tag.put("style", "display:none"); 890 } 891 892 /** 893 * Returns a content component. In case user haven't specified any content component, it returns 894 * an empty WebMarkupContainer. 895 * 896 * @return Content component 897 */ 898 protected final Component getContent() 899 { 900 return get(getContentId()); 901 } 902 903 /** 904 * Returns true if user has added own component to the window. 905 * 906 * @return True if user has added own component to the window, false otherwise. 907 */ 908 protected boolean isCustomComponent() 909 { 910 return getContent() != empty; 911 } 912 913 /** 914 * @see org.apache.wicket.MarkupContainer#remove(org.apache.wicket.Component) 915 */ 916 @Override 917 public ModalWindow remove(final Component component) 918 { 919 super.remove(component); 920 if (component.getId().equals(getContentId())) 921 { 922 add(empty = new WebMarkupContainer(getContentId())); 923 } 924 925 return this; 926 } 927 928 /** 929 * Sets the content of the modal window. 930 * 931 * @param component 932 * @return this; 933 */ 934 public ModalWindow setContent(final Component component) 935 { 936 if (component.getId().equals(getContentId()) == false) 937 { 938 throw new WicketRuntimeException("Modal window content id is wrong. Component ID:" + 939 component.getId() + "; content ID: " + getContentId()); 940 } 941 else if (component instanceof AbstractRepeater) 942 { 943 throw new WicketRuntimeException( 944 "A repeater component cannot be used as the content of a modal window, please use repeater's parent"); 945 } 946 947 component.setOutputMarkupPlaceholderTag(true); 948 component.setVisible(false); 949 replace(component); 950 shown = false; 951 pageCreator = null; 952 return this; 953 } 954 955 /** 956 * @author Matej Knopp 957 */ 958 private class WindowClosedBehavior extends AbstractDefaultAjaxBehavior 959 { 960 private static final long serialVersionUID = 1L; 961 962 @Override 963 protected void respond(final AjaxRequestTarget target) 964 { 965 shown = false; 966 967 if (windowClosedCallback != null) 968 { 969 windowClosedCallback.onClose(target); 970 } 971 } 972 } 973 974 /** 975 * @author Matej Knopp 976 */ 977 protected class CloseButtonBehavior extends AbstractDefaultAjaxBehavior 978 { 979 private static final long serialVersionUID = 1L; 980 981 public CloseButtonBehavior() 982 { 983 } 984 985 @Override 986 protected final void respond(final AjaxRequestTarget target) 987 { 988 if ((closeButtonCallback == null) || 989 (closeButtonCallback.onCloseButtonClicked(target))) 990 { 991 close(target); 992 } 993 } 994 } 995 996 /** 997 * Returns the markup id of the component. 998 * 999 * @return component id 1000 */ 1001 private String getContentMarkupId() 1002 { 1003 return getContent().getMarkupId(); 1004 } 1005 1006 /** 1007 * Returns the javascript used to open the window. Subclass 1008 * {@link #postProcessSettings(JSONObject)} to modify the JavaScript if needed. 1009 * 1010 * See WICKET-12 1011 * 1012 * @return javascript that opens the window 1013 */ 1014 protected final String getWindowOpenJavaScript() 1015 { 1016 JSONObject settings = new JSONObject(); 1017 1018 settings.put("minWidth", getMinimalWidth()); 1019 settings.put("minHeight", getMinimalHeight()); 1020 settings.put("className", getCssClassName()); 1021 settings.put("width", getInitialWidth()); 1022 1023 if ((isUseInitialHeight() == true) || (isCustomComponent() == false)) 1024 { 1025 settings.put("height", getInitialHeight()); 1026 } 1027 else 1028 { 1029 // WICKET-6613 null would remove the key, so use false instead 1030 settings.put("height", false); 1031 } 1032 1033 settings.put("resizable", isResizable()); 1034 1035 if (isResizable() == false) 1036 { 1037 settings.put("widthUnit", getWidthUnit()); 1038 settings.put("heightUnit", getHeightUnit()); 1039 } 1040 1041 if (isCustomComponent() == false) 1042 { 1043 Page page = createPage(); 1044 if (page == null) 1045 { 1046 throw new WicketRuntimeException("Error creating page for modal dialog."); 1047 } 1048 CharSequence pageUrl; 1049 RequestCycle requestCycle = RequestCycle.get(); 1050 1051 page.getSession().getPageManager().touchPage(page); 1052 if (page.isPageStateless()) 1053 { 1054 pageUrl = requestCycle.urlFor(page.getClass(), page.getPageParameters()); 1055 } 1056 else 1057 { 1058 IRequestHandler handler = new RenderPageRequestHandler(new PageProvider(page)); 1059 pageUrl = requestCycle.urlFor(handler); 1060 } 1061 1062 settings.put("src", pageUrl); 1063 } 1064 else 1065 { 1066 settings.put("element", new JSONFunction("document.getElementById(\"" + getContentMarkupId() + "\")")); 1067 } 1068 1069 if (getCookieName() != null) 1070 { 1071 settings.put("cookieId", getCookieName()); 1072 } 1073 1074 String title = getTitle() != null ? getTitle().getObject() : null; 1075 if (title != null) 1076 { 1077 settings.put("title", getDefaultModelObjectAsString(title)); 1078 } 1079 1080 if (getMaskType() == MaskType.TRANSPARENT) 1081 { 1082 settings.put("mask", "transparent"); 1083 } 1084 else if (getMaskType() == MaskType.SEMI_TRANSPARENT) 1085 { 1086 settings.put("mask", "semi-transparent"); 1087 } 1088 1089 settings.put("autoSize", autoSize); 1090 1091 settings.put("unloadConfirmation", showUnloadConfirmation()); 1092 1093 // set true if we set a windowclosedcallback 1094 boolean haveCloseCallback = false; 1095 1096 // in case user is interested in window close callback or we have a pagemap to clean attach 1097 // notification request 1098 if (windowClosedCallback != null) 1099 { 1100 WindowClosedBehavior behavior = getBehaviors(WindowClosedBehavior.class).get(0); 1101 settings.put("onClose", new JSONFunction("function() { " + behavior.getCallbackScript() + " }")); 1102 1103 haveCloseCallback = true; 1104 } 1105 1106 // in case we didn't set windowclosecallback, we need at least callback on close button, to 1107 // close window property (thus cleaning the shown flag) 1108 if ((closeButtonCallback != null) || (haveCloseCallback == false)) 1109 { 1110 CloseButtonBehavior behavior = getBehaviors(CloseButtonBehavior.class).get(0); 1111 settings.put("onCloseButton", new JSONFunction("function() { " + behavior.getCallbackScript() + "; return false; }")); 1112 } 1113 1114 postProcessSettings(settings); 1115 1116 AppendingStringBuffer buffer = new AppendingStringBuffer(500); 1117 buffer.append("var settings = "); 1118 buffer.append(settings.toString()); 1119 buffer.append(";"); 1120 1121 buffer.append(getShowJavaScript()); 1122 return buffer.toString(); 1123 } 1124 1125 /** 1126 * Method that allows tweaking the settings 1127 * 1128 * @param settings 1129 */ 1130 protected void postProcessSettings(JSONObject settings) 1131 { 1132 } 1133 1134 /** 1135 * Detach the 'title' model 1136 * 1137 * @see org.apache.wicket.Component#onDetach() 1138 */ 1139 @Override 1140 protected void onDetach() 1141 { 1142 super.onDetach(); 1143 1144 if (title != null) 1145 { 1146 title.detach(); 1147 } 1148 } 1149 1150 /** 1151 * Sets whether window size will be automatically adjusted on opening to fit content's width and 1152 * height. <span style="text-decoration: underline">Doesn't work on IE 6.</span> 1153 * 1154 * @param autoSize 1155 * Whether window size will be automatically adjusted 1156 * @return this 1157 */ 1158 public ModalWindow setAutoSize(final boolean autoSize) 1159 { 1160 this.autoSize = autoSize; 1161 return this; 1162 } 1163 1164 /** 1165 * Returns whether window will be opened in autosize mode. 1166 * 1167 * @return True if the window will be opened open in autosize mode, false otherwise 1168 */ 1169 public boolean isAutoSize() 1170 { 1171 return autoSize; 1172 } 1173 1174 /** 1175 * Gives the possibility to provide custom 1176 * {@link org.apache.wicket.ajax.attributes.IAjaxCallListener} 1177 * 1178 * @return the behavior that should be used for the window close button 1179 */ 1180 protected CloseButtonBehavior newCloseButtonBehavior() 1181 { 1182 return new CloseButtonBehavior(); 1183 } 1184}