001/*
002 *  Copyright 2012 GWT-Bootstrap
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package com.github.gwtbootstrap.client.ui;
017
018import java.util.HashSet;
019import java.util.Set;
020
021import com.github.gwtbootstrap.client.ui.base.DivWidget;
022import com.github.gwtbootstrap.client.ui.base.HasVisibility;
023import com.github.gwtbootstrap.client.ui.base.HasVisibleHandlers;
024import com.github.gwtbootstrap.client.ui.base.IsAnimated;
025import com.github.gwtbootstrap.client.ui.constants.BackdropType;
026import com.github.gwtbootstrap.client.ui.constants.Constants;
027import com.github.gwtbootstrap.client.ui.constants.DismissType;
028import com.github.gwtbootstrap.client.ui.event.HiddenEvent;
029import com.github.gwtbootstrap.client.ui.event.HiddenHandler;
030import com.github.gwtbootstrap.client.ui.event.HideEvent;
031import com.github.gwtbootstrap.client.ui.event.HideHandler;
032import com.github.gwtbootstrap.client.ui.event.ShowEvent;
033import com.github.gwtbootstrap.client.ui.event.ShowHandler;
034import com.github.gwtbootstrap.client.ui.event.ShownEvent;
035import com.github.gwtbootstrap.client.ui.event.ShownHandler;
036import com.google.gwt.dom.client.Element;
037import com.google.gwt.dom.client.Style;
038import com.google.gwt.event.shared.HandlerRegistration;
039import com.google.gwt.user.client.Event;
040import com.google.gwt.user.client.ui.PopupPanel;
041import com.google.gwt.user.client.ui.RootPanel;
042import com.google.gwt.user.client.ui.Widget;
043
044//@formatter:off
045/**
046 * Popup dialog with optional header and {@link ModalFooter footer.}
047 * <p>
048 * By default, all other Modals are closed once a new one is opened. This
049 * setting can be {@link #setHideOthers(boolean) overridden.}
050 * 
051 * <p>
052 * <h3>UiBinder Usage:</h3>
053 * 
054 * <pre>
055 * {@code
056 * <b:Modal title="My Modal" backdrop="STATIC">
057 *     <g:Label>Modal Content!</g:Label>
058 *     <b:ModalFooter>
059 *         <b:Button icon="FILE">Save</b:Button>
060 *     </b:ModalFooter>
061 * </b:Modal>
062 * }
063 * </pre>
064 * 
065 * All arguments are optional.
066 * </p>
067 * 
068 * @since 2.0.4.0
069 * 
070 * @author Carlos Alexandro Becker
071 * 
072 * @author Dominik Mayer
073 * 
074 * @see <a
075 *      href="http://twitter.github.com/bootstrap/javascript.html#modals">Bootstrap
076 *      documentation</a>
077 * @see PopupPanel
078 */
079// @formatter:on
080public class Modal extends DivWidget implements HasVisibility, HasVisibleHandlers, IsAnimated {
081
082        private static Set<Modal> currentlyShown = new HashSet<Modal>();
083
084        private final DivWidget header = new DivWidget();
085
086        private final DivWidget body = new DivWidget("modal-body");
087
088        private boolean keyboard = true;
089
090        private BackdropType backdropType = BackdropType.NORMAL;
091
092        private boolean show = false;
093
094        private boolean hideOthers = true;
095
096        private boolean configured = false;
097
098        private Close close = new Close(DismissType.MODAL);
099
100        private String title;
101
102        /**
103         * Creates an empty, hidden widget.
104         */
105        public Modal() {
106                super("modal");
107                super.add(header);
108                super.add(body);
109                setVisible(false);
110        }
111
112        /**
113         * Creates an empty, hidden widget with specified show behavior.
114         * 
115         * @param animated
116         *            <code>true</code> if the widget should be animated.
117         * 
118         */
119        public Modal(boolean animated) {
120                this(animated, false);
121        }
122
123        /**
124         * Creates an empty, hidden widget with specified show behavior.
125         * 
126         * @param animated
127         *            <code>true</code> if the widget should be animated.
128         * 
129         * @param dynamicSafe
130         *            <code>true</code> removes from RootPanel when hidden
131         */
132        public Modal(boolean animated,
133                boolean dynamicSafe) {
134                this();
135                setAnimation(animated);
136                setDynamicSafe(dynamicSafe);
137        }
138
139        /**
140         * Setup the modal to prevent memory leaks. When modal is hidden, will
141         * remove all event handlers, and them remove the modal DOM from document
142         * DOM.
143         * 
144         * Default is false.
145         * 
146         * @param dynamicSafe
147         */
148        public void setDynamicSafe(boolean dynamicSafe) {
149                if (dynamicSafe) {
150                        addHiddenHandler(new HiddenHandler() {
151
152                                @Override
153                                public void onHidden(HiddenEvent hiddenEvent) {
154                                        unsetHandlerFunctions(getElement());
155                                        Modal.this.removeFromParent();
156                                }
157                        });
158                }
159        }
160
161        /**
162         * Sets the title of the Modal.
163         * 
164         * @param title
165         *            the title of the Modal
166         */
167        @Override
168        public void setTitle(String title) {
169                this.title = title;
170
171                header.clear();
172                if (title == null || title.isEmpty()) {
173                        showHeader(false);
174                } else {
175
176                        header.add(close);
177                        header.add(new Heading(3, title));
178                        showHeader(true);
179                }
180        }
181
182        private void showHeader(boolean show) {
183                if (show)
184                        header.setStyleName(Constants.MODAL_HEADER);
185                else
186                        header.removeStyleName(Constants.MODAL_HEADER);
187        }
188
189        /**
190         * {@inheritDoc}
191         */
192        public void setAnimation(boolean animated) {
193                if (animated)
194                        addStyleName(Constants.FADE);
195                else
196                        removeStyleName(Constants.FADE);
197        }
198
199        /**
200         * {@inheritDoc}
201         */
202        public boolean getAnimation() {
203                return getStyleName().contains(Constants.FADE);
204        }
205
206        /**
207         * Sets whether this Modal appears on top of others or is the only one
208         * visible on screen.
209         * 
210         * @param hideOthers
211         *            <code>true</code> to make sure that this modal is the only one
212         *            shown. All others will be hidden. Default: <code>true</code>
213         */
214        public void setHideOthers(boolean hideOthers) {
215                this.hideOthers = hideOthers;
216        }
217
218        /**
219         * Sets whether the Modal is closed when the <code>ESC</code> is pressed.
220         * 
221         * @param keyboard
222         *            <code>true</code> if the Modal is closed by <code>ESC</code>
223         *            key. Default: <code>true</code>
224         */
225        public void setKeyboard(boolean keyboard) {
226                this.keyboard = keyboard;
227                reconfigure();
228        }
229
230        /**
231         * Get Keyboard enable state
232         * 
233         * @return true:enable false:disable
234         */
235        public boolean isKeyboardEnable() {
236                return this.keyboard;
237        }
238
239        /**
240         * Sets the type of the backdrop.
241         * 
242         * @param type
243         *            the backdrop type
244         */
245        public void setBackdrop(BackdropType type) {
246                backdropType = type;
247                reconfigure();
248
249        }
250
251        /**
252         * Get backdrop type.
253         * 
254         * @return backdrop type.
255         */
256        public BackdropType getBackdropType() {
257                return this.backdropType;
258        }
259
260        /**
261         * Reconfigures the modal with changed settings.
262         */
263        protected void reconfigure() {
264                if (configured) {
265                        reconfigure(keyboard, backdropType, show);
266                }
267        }
268
269        /**
270         * {@inheritDoc}
271         */
272        @Override
273        public void add(Widget w) {
274                if (w instanceof ModalFooter) {
275                        super.add(w);
276                } else
277                        body.add(w);
278        }
279
280        /**
281         * {@inheritDoc}
282         */
283        @Override
284        public void insert(Widget w, int beforeIndex) {
285                body.insert(w, beforeIndex);
286        }
287
288        /**
289         * {@inheritDoc}
290         */
291        public void show() {
292
293                if (!this.isAttached()) {
294
295                        RootPanel.get().add(this);
296                }
297
298                changeVisibility("show");
299        }
300
301        @Override
302        protected void onAttach() {
303                super.onAttach();
304                configure(keyboard, backdropType, show);
305                setHandlerFunctions(getElement());
306                configured = true;
307        }
308
309        /**
310         * {@inheritDoc}
311         */
312        public void hide() {
313                changeVisibility("hide");
314        }
315
316        /**
317         * {@inheritDoc}
318         */
319        public void toggle() {
320                changeVisibility("toggle");
321        }
322
323        private void changeVisibility(String visibility) {
324                changeVisibility(getElement(), visibility);
325        }
326
327        /**
328         * This method is called immediately when the widget's {@link #hide()}
329         * method is executed.
330         */
331        protected void onHide(Event e) {
332                fireEvent(new HideEvent(e));
333        }
334
335        /**
336         * This method is called once the widget is completely hidden.
337         */
338        protected void onHidden(Event e) {
339                fireEvent(new HiddenEvent(e));
340                currentlyShown.remove(this);
341        }
342
343        /**
344         * This method is called immediately when the widget's {@link #show()}
345         * method is executed.
346         */
347        protected void onShow(Event e) {
348                if (hideOthers)
349                        hideShownModals();
350                fireEvent(new ShowEvent(e));
351        }
352
353        private void hideShownModals() {
354                for (Modal m : currentlyShown) {
355                    if(!m.equals(this)) {
356                        m.hide();
357                    }               
358                }
359        }
360
361        /**
362         * This method is called once the widget is completely shown.
363         */
364        protected void onShown(Event e) {
365                fireEvent(new ShownEvent(e));
366                currentlyShown.add(this);
367        }
368
369        private void reconfigure(boolean keyboard, BackdropType backdropType, boolean show) {
370
371                if (backdropType == BackdropType.NORMAL) {
372                        reconfigure(getElement(), keyboard, true, show);
373                } else if (backdropType == BackdropType.NONE) {
374                        reconfigure(getElement(), keyboard, false, show);
375                } else if (backdropType == BackdropType.STATIC) {
376                        reconfigure(getElement(), keyboard, BackdropType.STATIC.get(), show);
377                }
378        }
379
380        private void configure(boolean keyboard, BackdropType backdropType, boolean show) {
381
382                if (backdropType == BackdropType.NORMAL) {
383                        configure(getElement(), keyboard, true, show);
384                } else if (backdropType == BackdropType.NONE) {
385                        configure(getElement(), keyboard, false, show);
386                } else if (backdropType == BackdropType.STATIC) {
387                        configure(getElement(), keyboard, BackdropType.STATIC.get(), show);
388                }
389        }
390
391        //@formatter:off
392        
393        private native void reconfigure(Element e, boolean k, boolean b, boolean s) /*-{
394                var modal = null;
395                if($wnd.jQuery(e).data('modal')) {
396                        modal = $wnd.jQuery(e).data('modal');
397                        $wnd.jQuery(e).removeData('modal');
398                }
399                $wnd.jQuery(e).modal({
400                                                keyboard : k,
401                                                backdrop : b,
402                                                show : s
403                                        });
404                                        
405                if(modal) {
406                        $wnd.jQuery(e).data('modal').isShown = modal.isShown;
407                }
408
409        }-*/;
410        private native void reconfigure(Element e, boolean k, String b, boolean s) /*-{
411                var modal = null;
412                if($wnd.jQuery(e).data('modal')) {
413                        modal = $wnd.jQuery(e).data('modal');
414                        $wnd.jQuery(e).removeData('modal');
415                }
416                $wnd.jQuery(e).modal({
417                                                keyboard : k,
418                                                backdrop : b,
419                                                show : s
420                                        });
421                                        
422                if(modal) {
423                        $wnd.jQuery(e).data('modal').isShown = modal.isShown;
424                }
425        }-*/;
426        
427        
428        private native void configure(Element e, boolean k, boolean b, boolean s) /*-{
429                $wnd.jQuery(e).modal({
430                                                        keyboard : k,
431                                                        backdrop : b,
432                                                        show : s
433                                                });
434
435        }-*/;
436        private native void configure(Element e, boolean k, String b, boolean s) /*-{
437                $wnd.jQuery(e).modal({
438                                                        keyboard : k,
439                                                        backdrop : b,
440                                                        show : s
441                                                });
442        }-*/;
443
444        private native void changeVisibility(Element e, String visibility) /*-{
445                $wnd.jQuery(e).modal(visibility);
446        }-*/;
447
448        /**
449         * Links the Java functions that fire the events.
450         */
451        private native void setHandlerFunctions(Element e) /*-{
452                var that = this;
453                $wnd.jQuery(e).on('hide', function(e) {
454                        that.@com.github.gwtbootstrap.client.ui.Modal::onHide(Lcom/google/gwt/user/client/Event;)(e);
455                });
456                $wnd.jQuery(e).on('hidden', function(e) {
457                        that.@com.github.gwtbootstrap.client.ui.Modal::onHidden(Lcom/google/gwt/user/client/Event;)(e);
458                });
459                $wnd.jQuery(e).on('show', function(e) {
460                        that.@com.github.gwtbootstrap.client.ui.Modal::onShow(Lcom/google/gwt/user/client/Event;)(e);
461                });
462                $wnd.jQuery(e).on('shown', function(e) {
463                        that.@com.github.gwtbootstrap.client.ui.Modal::onShown(Lcom/google/gwt/user/client/Event;)(e);
464                });
465        }-*/;
466
467        /**
468         * Unlinks all the Java functions that fire the events.
469         */
470        private native void unsetHandlerFunctions(Element e) /*-{
471                $wnd.jQuery(e).off('hide');
472                $wnd.jQuery(e).off('hidden');
473                $wnd.jQuery(e).off('show');
474                $wnd.jQuery(e).off('shown');
475        }-*/;
476        //@formatter:on
477
478        /**
479         * {@inheritDoc}
480         */
481        public HandlerRegistration addHideHandler(HideHandler handler) {
482                return addHandler(handler, HideEvent.getType());
483        }
484
485        /**
486         * {@inheritDoc}
487         */
488        public HandlerRegistration addHiddenHandler(HiddenHandler handler) {
489                return addHandler(handler, HiddenEvent.getType());
490        }
491
492        /**
493         * {@inheritDoc}
494         */
495        public HandlerRegistration addShowHandler(ShowHandler handler) {
496                return addHandler(handler, ShowEvent.getType());
497        }
498
499        /**
500         * {@inheritDoc}
501         */
502        public HandlerRegistration addShownHandler(ShownHandler handler) {
503                return addHandler(handler, ShownEvent.getType());
504        }
505
506        /**
507         * Show/Hide close button. The Modal must have a title.
508         * 
509         * @param visible
510         *            <b>true</b> for show and <b>false</b> to hide. Defaults is
511         *            <b>true</b>.
512         */
513        public void setCloseVisible(boolean visible) {
514                close.getElement().getStyle().setVisibility(visible
515                                                                                                                        ? Style.Visibility.VISIBLE
516                                                                                                                        : Style.Visibility.HIDDEN);
517        }
518        
519        /**
520         * @deprecated modal do not support setSize method
521         */
522        @Override
523        public void setSize(String width, String height) {
524                throw new UnsupportedOperationException("modal do not support setSize method");
525        }
526
527}