001/* 002 * Copyright 2013 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 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software 010 * distributed under the License is distributed on an "AS IS" BASIS, 011 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 * See the License for the specific language governing permissions and 013 * limitations under the License. 014 */ 015package com.github.gwtbootstrap.client.ui; 016 017import com.github.gwtbootstrap.client.ui.base.DivWidget; 018import com.github.gwtbootstrap.client.ui.base.HasVisibility; 019import com.github.gwtbootstrap.client.ui.event.HasVisibleHandlers; 020import com.github.gwtbootstrap.client.ui.base.IsAnimated; 021import com.github.gwtbootstrap.client.ui.constants.BackdropType; 022import com.github.gwtbootstrap.client.ui.constants.Constants; 023import com.github.gwtbootstrap.client.ui.constants.DismissType; 024import com.github.gwtbootstrap.client.ui.event.*; 025import com.google.gwt.core.client.JavaScriptObject; 026import com.google.gwt.dom.client.Element; 027import com.google.gwt.dom.client.Style; 028import com.google.gwt.event.shared.HandlerRegistration; 029import com.google.gwt.user.client.DOM; 030import com.google.gwt.user.client.Event; 031import com.google.gwt.user.client.ui.PopupPanel; 032import com.google.gwt.user.client.ui.RootPanel; 033import com.google.gwt.user.client.ui.Widget; 034 035import java.util.HashSet; 036import java.util.Set; 037 038//@formatter:off 039 040/** 041 * Popup dialog with optional header and {@link ModalFooter footer.} 042 * <p> 043 * By default, all other Modals are closed once a new one is opened. This 044 * setting can be {@link #setHideOthers(boolean) overridden.} 045 * <p/> 046 * <p> 047 * <h3>UiBinder Usage:</h3> 048 * <p/> 049 * <pre> 050 * {@code 051 * <b:Modal title="My Modal" backdrop="STATIC"> 052 * <g:Label>Modal Content!</g:Label> 053 * <b:ModalFooter> 054 * <b:Button icon="FILE">Save</b:Button> 055 * </b:ModalFooter> 056 * </b:Modal> 057 * } 058 * </pre> 059 * <p/> 060 * All arguments are optional. 061 * </p> 062 * 063 * @author Carlos Alexandro Becker 064 * @author Dominik Mayer 065 * @author Danilo Reinert 066 * 067 * @see <a 068 * href="http://twitter.github.com/bootstrap/javascript.html#modals">Bootstrap 069 * documentation</a> 070 * @see PopupPanel 071 * 072 * @since 2.0.4.0 073 */ 074// @formatter:on 075public class Modal extends DivWidget implements HasVisibility, HasVisibleHandlers, IsAnimated { 076 077 private static Set<Modal> currentlyShown = new HashSet<Modal>(); 078 079 private final DivWidget header = new DivWidget(); 080 081 private final DivWidget body = new DivWidget("modal-body"); 082 083 private boolean keyboard = true; 084 085 private BackdropType backdropType = BackdropType.NORMAL; 086 087 private boolean show = false; 088 089 private boolean hideOthers = true; 090 091 private boolean configured = false; 092 093 private Close close = new Close(DismissType.MODAL); 094 095 private String title; 096 097 /** 098 * Creates an empty, hidden widget. 099 */ 100 public Modal() { 101 super("modal"); 102 getElement().setAttribute("tabindex", "-1"); 103 super.add(header); 104 super.add(body); 105 setVisible(false); 106 } 107 108 /** 109 * Creates an empty, hidden widget with specified show behavior. 110 * 111 * @param animated <code>true</code> if the widget should be animated. 112 */ 113 public Modal(boolean animated) { 114 this(animated, false); 115 } 116 117 /** 118 * Creates an empty, hidden widget with specified show behavior. 119 * 120 * @param animated <code>true</code> if the widget should be animated. 121 * @param dynamicSafe <code>true</code> removes from RootPanel when hidden 122 */ 123 public Modal(boolean animated, 124 boolean dynamicSafe) { 125 this(); 126 setAnimation(animated); 127 setDynamicSafe(dynamicSafe); 128 } 129 130 /** 131 * Setup the modal to prevent memory leaks. When modal is hidden, will 132 * remove all event handlers, and them remove the modal DOM from document 133 * DOM. 134 * <p/> 135 * Default is false. 136 * 137 * @param dynamicSafe 138 */ 139 public void setDynamicSafe(boolean dynamicSafe) { 140 if (dynamicSafe) { 141 addHiddenHandler(new HiddenHandler() { 142 143 @Override 144 public void onHidden(HiddenEvent hiddenEvent) { 145 unsetHandlerFunctions(getElement()); 146 Modal.this.removeFromParent(); 147 } 148 }); 149 } 150 } 151 152 /** 153 * Sets the title of the Modal. 154 * 155 * @param title the title of the Modal 156 */ 157 @Override 158 public void setTitle(String title) { 159 this.title = title; 160 161 header.clear(); 162 if (title == null || title.isEmpty()) { 163 showHeader(false); 164 } else { 165 166 header.add(close); 167 header.add(new Heading(3, title)); 168 showHeader(true); 169 } 170 } 171 172 /** 173 * Gets the title of the Modal. 174 * 175 * @return title 176 */ 177 public String getTitle() { 178 return title; 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 <code>true</code> to make sure that this modal is the only one 210 * shown. All others will be hidden. Default: <code>true</code> 211 */ 212 public void setHideOthers(boolean hideOthers) { 213 this.hideOthers = hideOthers; 214 } 215 216 /** 217 * Sets whether the Modal is closed when the <code>ESC</code> is pressed. 218 * 219 * @param keyboard <code>true</code> if the Modal is closed by <code>ESC</code> 220 * key. Default: <code>true</code> 221 */ 222 public void setKeyboard(boolean keyboard) { 223 this.keyboard = keyboard; 224 reconfigure(); 225 } 226 227 /** 228 * Get Keyboard enable state 229 * 230 * @return true:enable false:disable 231 */ 232 public boolean isKeyboardEnable() { 233 return this.keyboard; 234 } 235 236 /** 237 * Sets the type of the backdrop. 238 * 239 * @param type the backdrop type 240 */ 241 public void setBackdrop(BackdropType type) { 242 backdropType = type; 243 reconfigure(); 244 245 } 246 247 /** 248 * Get backdrop type. 249 * 250 * @return backdrop type. 251 */ 252 public BackdropType getBackdropType() { 253 return this.backdropType; 254 } 255 256 /** 257 * Reconfigures the modal with changed settings. 258 */ 259 protected void reconfigure() { 260 if (configured) { 261 reconfigure(keyboard, backdropType, show); 262 } 263 } 264 265 /** 266 * {@inheritDoc} 267 */ 268 @Override 269 public void add(Widget w) { 270 if (w instanceof ModalFooter) { 271 super.add(w); 272 } else 273 body.add(w); 274 } 275 276 /** 277 * {@inheritDoc} 278 */ 279 @Override 280 public void insert(Widget w, int beforeIndex) { 281 body.insert(w, beforeIndex); 282 } 283 284 /** 285 * {@inheritDoc} 286 */ 287 public void show() { 288 checkAttachedOnShow(); 289 changeVisibility("show"); 290 // centerVertically(getElement()); Note: Doesn't work with Bootstrap 2.3.2 291 } 292 293 /** 294 * {@inheritDoc} 295 */ 296 public void show(boolean autoShow) { 297 checkAttachedOnShow(); 298 changeVisibility("show", autoShow); 299 // centerVertically(getElement()); Note: Doesn't work with Bootstrap 2.3.2 300 } 301 302 private void checkAttachedOnShow() { 303 if (!this.isAttached()) { 304 RootPanel.get().add(this); 305 } 306 } 307 308 @Override 309 protected void onAttach() { 310 super.onAttach(); 311 configure(keyboard, backdropType, show); 312 setHandlerFunctions(getElement()); 313 configured = true; 314 } 315 316 /** 317 * {@inheritDoc} 318 */ 319 public void hide() { 320 changeVisibility("hide"); 321 } 322 323 /** 324 * {@inheritDoc} 325 */ 326 public void hide(boolean autoHidden) { 327 changeVisibility("hide", autoHidden); 328 } 329 330 /** 331 * {@inheritDoc} 332 */ 333 public void toggle() { 334 changeVisibility("toggle"); 335 } 336 337 /** 338 * {@inheritDoc} 339 */ 340 public void toggle(boolean autoToggle) { 341 changeVisibility("toggle", autoToggle); 342 } 343 344 private void changeVisibility(String visibility) { 345 changeVisibility(getElement(), visibility); 346 } 347 348 private void changeVisibility(String visibility, boolean autoTriggered) { 349 changeVisibility(getElement(), visibility, autoTriggered); 350 } 351 352 /** 353 * This method is called immediately when the widget's {@link #hide()} 354 * method is executed. 355 */ 356 protected void onHide(Event e) { 357 fireEvent(new HideEvent(e, getAutoTriggered(e))); 358 } 359 360 /** 361 * This method is called once the widget is completely hidden. 362 */ 363 protected void onHidden(Event e) { 364 fireEvent(new HiddenEvent(e, getAutoTriggered(e))); 365 currentlyShown.remove(this); 366 } 367 368 /** 369 * This method is called immediately when the widget's {@link #show()} 370 * method is executed. 371 */ 372 protected void onShow(Event e) { 373 if (hideOthers) 374 hideShownModals(); 375 fireEvent(new ShowEvent(e, getAutoTriggered(e))); 376 } 377 378 private void hideShownModals() { 379 for (Modal m : currentlyShown) { 380 if (!m.equals(this)) { 381 m.hide(true); 382 } 383 } 384 } 385 386 /** 387 * This method is called once the widget is completely shown. 388 */ 389 protected void onShown(Event e) { 390 fireEvent(new ShownEvent(e, getAutoTriggered(e))); 391 currentlyShown.add(this); 392 } 393 394 private void reconfigure(boolean keyboard, BackdropType backdropType, boolean show) { 395 if (backdropType == BackdropType.NORMAL) { 396 reconfigure(getElement(), keyboard, true, show); 397 } else if (backdropType == BackdropType.NONE) { 398 reconfigure(getElement(), keyboard, false, show); 399 } else if (backdropType == BackdropType.STATIC) { 400 reconfigure(getElement(), keyboard, BackdropType.STATIC.get(), show); 401 } 402 } 403 404 private void configure(boolean keyboard, BackdropType backdropType, boolean show) { 405 if (backdropType == BackdropType.NORMAL) { 406 configure(getElement(), keyboard, true, show); 407 } else if (backdropType == BackdropType.NONE) { 408 configure(getElement(), keyboard, false, show); 409 } else if (backdropType == BackdropType.STATIC) { 410 configure(getElement(), keyboard, BackdropType.STATIC.get(), show); 411 } 412 } 413 414 //@formatter:off 415 416 private native void reconfigure(Element e, boolean k, boolean b, boolean s) /*-{ 417 // Init vars 418 var $e = $wnd.jQuery(e); 419 var modal = $e.data('modal'); 420 var wasShown = null; 421 422 // If element is modal, then unset it 423 if (modal) { 424 $e.removeData('modal'); 425 wasShown = modal.isShown; 426 } 427 428 // Apply modal again to the element 429 $e.modal({ 430 keyboard: k, 431 backdrop: b, 432 show: s 433 }); 434 435 // If previous modal was shown, then reset it to current modal 436 if (wasShown) { 437 $e.data('modal').isShown = wasShown; 438 } 439 }-*/; 440 441 private native void reconfigure(Element e, boolean k, String b, boolean s) /*-{ 442 // Init vars 443 var $e = $wnd.jQuery(e); 444 var modal = $e.data('modal'); 445 var wasShown = null; 446 447 // If element is modal, then unset it 448 if (modal) { 449 $e.removeData('modal'); 450 wasShown = modal.isShown; 451 } 452 453 // Apply modal again to the element 454 $e.modal({ 455 keyboard: k, 456 backdrop: b, 457 show: s 458 }); 459 460 // If previous modal was shown, then reset it to current modal 461 if (wasShown) { 462 $e.data('modal').isShown = wasShown; 463 } 464 }-*/; 465 466 467 private native void configure(Element e, boolean k, boolean b, boolean s) /*-{ 468 $wnd.jQuery(e).modal({ 469 keyboard: k, 470 backdrop: b, 471 show: s 472 }); 473 }-*/; 474 475 private native void configure(Element e, boolean k, String b, boolean s) /*-{ 476 $wnd.jQuery(e).modal({ 477 keyboard: k, 478 backdrop: b, 479 show: s 480 }); 481 }-*/; 482 483 private native void changeVisibility(Element e, String visibility) /*-{ 484 $wnd.jQuery(e).modal(visibility); 485 }-*/; 486 487 private native void changeVisibility(Element e, String visibility, boolean autoTriggered) /*-{ 488 var $e = $wnd.jQuery(e); 489 490 var modal = $e.data('modal'); 491 if (modal) 492 modal.autoTriggered = autoTriggered; 493 494 $e.modal(visibility); 495 }-*/; 496 497 private native boolean getAutoTriggered(JavaScriptObject jso) /*-{ 498 // Prevent null result 499 if (jso.autoTriggered) return true; 500 return false; 501 }-*/; 502 503 /** 504 * Links the Java functions that fire the events. 505 */ 506 private native void setHandlerFunctions(Element element) /*-{ 507 var that = this; 508 var $el = $wnd.jQuery(element); 509 var autoTriggeredCheck = function (event, removeProperty) { 510 var modal = $wnd.jQuery(event.target).data('modal'); 511 if (modal && modal.autoTriggered) { 512 event.autoTriggered = true; 513 if (removeProperty) 514 modal.autoTriggered = false; 515 } 516 }; 517 518 $el.on('hide', function (e) { 519 if (e.target === this) { 520 autoTriggeredCheck(e); 521 that.@com.github.gwtbootstrap.client.ui.Modal::onHide(Lcom/google/gwt/user/client/Event;)(e); 522 e.stopPropagation(); 523 } 524 }); 525 $el.on('hidden', function (e) { 526 if (e.target === this) { 527 autoTriggeredCheck(e, true); 528 that.@com.github.gwtbootstrap.client.ui.Modal::onHidden(Lcom/google/gwt/user/client/Event;)(e); 529 e.stopPropagation(); 530 } 531 }); 532 $el.on('show', function (e) { 533 if (e.target === this) { 534 autoTriggeredCheck(e); 535 that.@com.github.gwtbootstrap.client.ui.Modal::onShow(Lcom/google/gwt/user/client/Event;)(e); 536 e.stopPropagation(); 537 } 538 }); 539 $el.on('shown', function (e) { 540 if (e.target === this) { 541 autoTriggeredCheck(e, true); 542 that.@com.github.gwtbootstrap.client.ui.Modal::onShown(Lcom/google/gwt/user/client/Event;)(e); 543 e.stopPropagation(); 544 } 545 }); 546 }-*/; 547 548 /** 549 * Unlinks all the Java functions that fire the events. 550 */ 551 private native void unsetHandlerFunctions(Element e) /*-{ 552 var $e = $wnd.jQuery(e); 553 $e.off('hide'); 554 $e.off('hidden'); 555 $e.off('show'); 556 $e.off('shown'); 557 }-*/; 558 //@formatter:on 559 560 /** 561 * {@inheritDoc} 562 */ 563 public HandlerRegistration addHideHandler(HideHandler handler) { 564 return addHandler(handler, HideEvent.getType()); 565 } 566 567 /** 568 * {@inheritDoc} 569 */ 570 public HandlerRegistration addHiddenHandler(HiddenHandler handler) { 571 return addHandler(handler, HiddenEvent.getType()); 572 } 573 574 /** 575 * {@inheritDoc} 576 */ 577 public HandlerRegistration addShowHandler(ShowHandler handler) { 578 return addHandler(handler, ShowEvent.getType()); 579 } 580 581 /** 582 * {@inheritDoc} 583 */ 584 public HandlerRegistration addShownHandler(ShownHandler handler) { 585 return addHandler(handler, ShownEvent.getType()); 586 } 587 588 /** 589 * Show/Hide close button. The Modal must have a title. 590 * 591 * @param visible <b>true</b> for show and <b>false</b> to hide. Defaults is 592 * <b>true</b>. 593 */ 594 public void setCloseVisible(boolean visible) { 595 close.getElement().getStyle().setVisibility(visible 596 ? Style.Visibility.VISIBLE 597 : Style.Visibility.HIDDEN); 598 } 599 600 /** 601 * @deprecated modal do not support setSize method 602 */ 603 @Override 604 public void setSize(String width, String height) { 605 throw new UnsupportedOperationException("modal do not support setSize method"); 606 } 607 608 /** 609 * Sets the Modal's width. 610 * 611 * @param width Modal's new width, in px 612 */ 613 public void setWidth(int width) { 614 DOM.setStyleAttribute(this.getElement(), "width", width + "px"); 615 DOM.setStyleAttribute(this.getElement(), "marginLeft", (-width / 2) + "px"); 616 } 617 618 /** 619 * Sets the Modal's body maxHeight. 620 * 621 * @param maxHeight the Modal's body new maxHeight, in CSS units (e.g. "10px", "1em") 622 */ 623 public void setMaxHeigth(String maxHeight) { 624 DOM.setStyleAttribute(body.getElement(), "maxHeight", maxHeight); 625 } 626 627}