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