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 &lt;div&gt; when used with a Panel or &lt;iframe&gt;
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 &lt;span&gt; tag, make sure
057 * you use a &lt;div&gt;.
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, &quot;window&quot;);
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}