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}