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 that.@com.github.gwtbootstrap.client.ui.Modal::onHide()();
451 });
452 $wnd.jQuery(e).on('hidden', function() {
453 that.@com.github.gwtbootstrap.client.ui.Modal::onHidden()();
454 });
455 $wnd.jQuery(e).on('show', function() {
456 that.@com.github.gwtbootstrap.client.ui.Modal::onShow()();
457 });
458 $wnd.jQuery(e).on('shown', function() {
459 that.@com.github.gwtbootstrap.client.ui.Modal::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 }