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