001 /** 002 * Copyright 2010 The Kuali Foundation Licensed under the 003 * Educational Community License, Version 2.0 (the "License"); you may 004 * not use this file except in compliance with the License. You may 005 * obtain a copy of the License at 006 * 007 * http://www.osedu.org/licenses/ECL-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, 010 * software distributed under the License is distributed on an "AS IS" 011 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 012 * or implied. See the License for the specific language governing 013 * permissions and limitations under the License. 014 */ 015 016 package org.kuali.student.common.ui.client.widgets; 017 018 import java.util.Arrays; 019 import java.util.List; 020 021 import com.google.gwt.dom.client.Element; 022 import com.google.gwt.dom.client.NativeEvent; 023 import com.google.gwt.dom.client.NodeList; 024 import com.google.gwt.dom.client.Style.Overflow; 025 import com.google.gwt.dom.client.Style.Unit; 026 import com.google.gwt.event.dom.client.ClickEvent; 027 import com.google.gwt.event.dom.client.ClickHandler; 028 import com.google.gwt.event.dom.client.KeyCodes; 029 import com.google.gwt.event.logical.shared.ResizeEvent; 030 import com.google.gwt.event.logical.shared.ResizeHandler; 031 import com.google.gwt.event.shared.HandlerRegistration; 032 import com.google.gwt.user.client.Command; 033 import com.google.gwt.user.client.DeferredCommand; 034 import com.google.gwt.user.client.Event.NativePreviewEvent; 035 import com.google.gwt.user.client.Window; 036 import com.google.gwt.user.client.ui.Anchor; 037 import com.google.gwt.user.client.ui.DialogBox; 038 import com.google.gwt.user.client.ui.DockLayoutPanel; 039 import com.google.gwt.user.client.ui.FlowPanel; 040 import com.google.gwt.user.client.ui.ScrollPanel; 041 import com.google.gwt.user.client.ui.SimplePanel; 042 import com.google.gwt.user.client.ui.Widget; 043 044 /** 045 * A KSLightBox is a dialog box that lays out its contents as follows using a 046 * {@link DockLayoutPanel}:<br/> 047 * <br /> 048 * ------------------<br/> 049 * HEADER (either static caption / header widget that is not part of the scrollable area)<br/> 050 * ------------------<br/> 051 * CONTENT (part of the scrollable area that will fill available space)<br/> 052 * ------------------<br/> 053 * BUTTONS (static button area that is not part of the scrollable area)<br/> 054 * ------------------<br/> 055 * <br/> 056 * 057 * The size of the dock panel will determine the size of the lightbox. Thus only the dock 058 * panel size will need to be set.<br/> 059 * The size of the dock panel is usually determined dynamically when the lightbox is 060 * displayed, but you can also statically set it with one of the 'setSize' methods.<br/> 061 * If you however set the size statically, the lightbox won't resize.<br/> 062 * If you're making use of the <b>static sizes, the non-caption header and displaying the 063 * buttons</b>, don't set the height of the dialog smaller than about 155px. Otherwise 064 * there's too little space left for the content and the content (even if its just one line 065 * of text) will be displayed in a scroll panel. 066 * 067 */ 068 public class KSLightBox extends DialogBox { 069 070 private static final List<String> FOCUSABLE_TAGS = Arrays.asList("INPUT", "SELECT", "BUTTON", "TEXTAREA"); 071 072 /** 073 * An enum that specifies predefined width and height values (in pixels) for the 074 * lightbox. 075 */ 076 public enum Size { 077 SMALL(400, 155), MEDIUM(550, 340), LARGE(600, 400); 078 079 private int width; 080 081 private int height; 082 083 Size(int width, int height) { 084 this.width = width; 085 this.height = height; 086 } 087 088 public int getWidth() { 089 return width; 090 } 091 092 public int getHeight() { 093 return height; 094 } 095 } 096 097 private enum LightBoxStyle { 098 LIGHT_BOX { 099 public String toString() { 100 return "ks-lightbox"; 101 } 102 }, 103 MAIN_PANEL { 104 public String toString() { 105 return "ks-lightbox-mainPanel"; 106 } 107 }, 108 TITLE_PANEL { 109 public String toString() { 110 return "ks-lightbox-titlePanel"; 111 } 112 }, 113 SCROLL_PANEL { 114 public String toString() { 115 return "ks-lightbox-scrollPanel"; 116 } 117 }, 118 BUTTON_PANEL { 119 public String toString() { 120 return "ks-lightbox-buttonPanel"; 121 } 122 }, 123 CLOSE_LINK { 124 public String toString() { 125 return "ks-lightbox-closeLink"; 126 } 127 }, 128 CLOSE_LINK_WITH_CAPTION { 129 public String toString() { 130 return "ks-lightbox-closeLink-with-Caption"; 131 } 132 } 133 } 134 135 //Resizing variables 136 private int maxWidth = 800; 137 private int maxHeight = 0; 138 private int minWidth = 400; 139 private int minHeight = 200; 140 private int permWidth = -1; 141 private int permHeight = -1; 142 143 //DockLayoutPanel sizes 144 private static final int BUTTON_HEIGHT = 40;//'px' units (specified in mainPanel's constructor) 145 private static final int NON_CAPTION_HEIGHT = 40;//'px' units (specified in mainPanel's constructor) 146 147 private DockLayoutPanel mainPanel = new DockLayoutPanel(Unit.PX); 148 private FlowPanel closeLinkPanel = new FlowPanel(); 149 private SimplePanel titlePanel = new SimplePanel(); 150 private ScrollPanel scrollPanel = new ScrollPanel(); 151 private SimplePanel contentPanel = new SimplePanel(); 152 private FlowPanel buttonPanel = new FlowPanel(); 153 private Anchor closeLink = new Anchor(); 154 155 private KSDialogResizeHandler resizeHandler = new KSDialogResizeHandler(); 156 private HandlerRegistration resizeHandlerRegistrater; 157 158 public KSLightBox() { 159 getCaption().asWidget().setVisible(false); 160 init(); 161 } 162 163 public KSLightBox(boolean addCloseLink) { 164 getCaption().asWidget().setVisible(false); 165 init(); 166 closeLink.setVisible(addCloseLink); 167 } 168 169 public KSLightBox(String title) { 170 init(); 171 setText(title); 172 } 173 174 public KSLightBox(String title, Size size) { 175 init(); 176 setText(title); 177 setSize(size); 178 } 179 180 private void init() { 181 mainPanel.setStyleName(LightBoxStyle.MAIN_PANEL.toString()); 182 titlePanel.setStyleName(LightBoxStyle.TITLE_PANEL.toString()); 183 closeLink.setStyleName(LightBoxStyle.CLOSE_LINK.toString()); 184 scrollPanel.setStyleName(LightBoxStyle.SCROLL_PANEL.toString()); 185 buttonPanel.setStyleName(LightBoxStyle.BUTTON_PANEL.toString()); 186 187 setGlassEnabled(true); 188 super.setWidget(mainPanel); 189 mainPanel.addNorth(closeLinkPanel, 1); 190 mainPanel.addNorth(titlePanel, 0); 191 mainPanel.addSouth(buttonPanel, BUTTON_HEIGHT); 192 mainPanel.add(scrollPanel); 193 closeLinkPanel.add(closeLink); 194 //parent element sets overflow to hidden to allow background and other css styling on the titlePanel 195 Element titlePanelContainer = mainPanel.getWidgetContainerElement(titlePanel); 196 titlePanelContainer.getStyle().setOverflow(Overflow.VISIBLE); 197 //parent element sets overflow to hidden, must reset overflow to visible to show the 'closeLink' 198 Element closeLinkPanelContainer = mainPanel.getWidgetContainerElement(closeLinkPanel); 199 closeLinkPanelContainer.getStyle().setOverflow(Overflow.VISIBLE); 200 scrollPanel.add(contentPanel); 201 202 installResizeHandler(); 203 //super. 204 closeLink.addClickHandler(new ClickHandler() { 205 206 @Override 207 public void onClick(ClickEvent event) { 208 hide(); 209 } 210 }); 211 super.setStyleName(LightBoxStyle.LIGHT_BOX.toString()); 212 213 } 214 215 public void setCloseLinkVisible(boolean visible) { 216 closeLink.setVisible(visible); 217 } 218 219 public void removeCloseLink() { 220 closeLink.setVisible(false); 221 } 222 223 public HandlerRegistration addCloseLinkClickHandler(ClickHandler clickHandler) { 224 return closeLink.addClickHandler(clickHandler); 225 } 226 227 /** 228 * Sets the header that will be displayed at the top of the lightbox.<br/> 229 * Please note: This header will not be displayed in the caption, but in the actual 230 * lightbox content area. 231 * @param widget The header widget. 232 */ 233 public void setNonCaptionHeader(Widget widget) { 234 titlePanel.setWidget(widget); 235 mainPanel.setWidgetSize(titlePanel, NON_CAPTION_HEIGHT); 236 mainPanel.forceLayout(); 237 } 238 239 @Override 240 public void setText(String text) { 241 super.setText(text); 242 getCaption().asWidget().setVisible(true); 243 //Style needed to reposition the 'closeLink' 244 closeLink.addStyleName(LightBoxStyle.CLOSE_LINK_WITH_CAPTION.toString()); 245 } 246 247 /** 248 * Removes all the buttons at the bottom of the lightbox. 249 */ 250 public void clearButtons() { 251 buttonPanel.clear(); 252 } 253 254 /** 255 * Adds a button to the bottom of the lightbox. 256 */ 257 public void addButton(Widget button) { 258 button.addStyleName("ks-button-spacing"); 259 buttonPanel.add(button); 260 } 261 262 /** 263 * Adds a {@link org.kuali.student.common.ui.client.widgets.field.layout.button.ButtonGroup} to the button panel at the bottom of the lightbox. 264 * @param group 265 */ 266 @SuppressWarnings("rawtypes") 267 public void addButtonGroup(org.kuali.student.common.ui.client.widgets.field.layout.button.ButtonGroup group) { 268 buttonPanel.add(group); 269 } 270 271 /** 272 * Adds a {@link org.kuali.student.common.ui.client.widgets.buttongroups.ButtonGroup} to the button panel at the bottom of the lightbox. 273 * @param group 274 */ 275 @SuppressWarnings("rawtypes") 276 public void addButtonGroup(org.kuali.student.common.ui.client.widgets.buttongroups.ButtonGroup group) { 277 buttonPanel.add(group); 278 279 } 280 281 public void showButtons(boolean show) { 282 buttonPanel.setVisible(show); 283 if (show) { 284 mainPanel.setWidgetSize(buttonPanel, BUTTON_HEIGHT); 285 } else { 286 mainPanel.setWidgetSize(buttonPanel, 0); 287 } 288 mainPanel.forceLayout(); 289 } 290 291 /** 292 * Set the maximum width in pixels that this dialog will grow to.<br /> 293 * Please note: If the lightbox's size was set explicitly, this call will have no 294 * effect. 295 * 296 * @param width The dialog's maximum width in pixels. 297 */ 298 public void setMaxWidth(int width) { 299 this.maxWidth = width; 300 } 301 302 /** 303 * Set the maximum height in pixels that this dialog will grow to.<br /> 304 * Please note: If the lightbox's size was set explicitly, this call will have no 305 * effect. 306 * 307 * @param height The dialog's maximum height in pixels. 308 */ 309 public void setMaxHeight(int height) { 310 this.maxHeight = height; 311 } 312 313 /** 314 * Set the width and height of the lightbox in pixels.<br/> 315 * Please note: These values will not be affected by resizing. Thus the lightbox will 316 * remain the specified size, irrespective of resizing. 317 * 318 * @param width The specified width in pixels. 319 * @param height The specified height in pixels. 320 */ 321 public void setSize(int width, int height) { 322 mainPanel.setSize(width + "px", width + "px"); 323 this.permHeight = height; 324 this.permWidth = width; 325 } 326 327 /** 328 * Set the width and height of the lightbox in pixels using the {@link Size} enum. 329 * 330 * @param size A predefined dialog size. 331 */ 332 public void setSize(Size size) { 333 setSize(size.getWidth(), size.getHeight()); 334 } 335 336 @Override 337 public void setWidget(Widget content) { 338 contentPanel.setWidget(content); 339 } 340 341 @Override 342 public Widget getWidget() { 343 return contentPanel.getWidget(); 344 } 345 346 @Override 347 public void hide() { 348 super.hide(); 349 uninstallResizeHandler(); 350 } 351 352 @Override 353 public void show() { 354 resizeDialog(); 355 installResizeHandler(); 356 super.show(); 357 super.center(); 358 grabFocus(); 359 } 360 361 private void resizeDialog() { 362 363 int width = maxWidth; 364 int height = maxHeight; 365 366 //Width calculation 367 if (permWidth != -1) { 368 width = permWidth; 369 } else { 370 if (Window.getClientWidth() < 850) { 371 width = Window.getClientWidth() - 160; 372 } 373 if (width > maxWidth) { 374 width = maxWidth; 375 } 376 if (width < minWidth) { 377 width = minWidth; 378 } 379 } 380 381 //Height calculation 382 if (permHeight != -1) { 383 height = permHeight; 384 } else { 385 height = Window.getClientHeight() - 160; 386 387 if (height > maxHeight && maxHeight != 0) { 388 height = maxHeight; 389 } 390 if (height < minHeight) { 391 height = minHeight; 392 } 393 } 394 395 if (width > 0 && height > 0) { 396 mainPanel.setSize(width + "px", height + "px"); 397 } 398 399 } 400 401 private void grabFocus() { 402 Widget mainContent = contentPanel.getWidget(); 403 NodeList<Element> nodeList = mainContent.getElement().getElementsByTagName("*"); 404 for (int i = 0; i < nodeList.getLength(); i++) { 405 Element e = nodeList.getItem(i); 406 if (FOCUSABLE_TAGS.contains(e.getTagName().toUpperCase())) { 407 e.focus(); 408 return; 409 } 410 } 411 412 } 413 414 @Override 415 protected void onPreviewNativeEvent(NativePreviewEvent preview) { 416 super.onPreviewNativeEvent(preview); 417 NativeEvent evt = preview.getNativeEvent(); 418 if (evt.getType().equals("keydown")) { 419 switch (evt.getKeyCode()) { 420 case KeyCodes.KEY_ESCAPE: 421 hide(); 422 break; 423 } 424 } 425 } 426 427 public void uninstallResizeHandler() { 428 if (resizeHandlerRegistrater != null) { 429 resizeHandlerRegistrater.removeHandler(); 430 resizeHandlerRegistrater = null; 431 432 } 433 } 434 435 public void installResizeHandler() { 436 if (resizeHandlerRegistrater == null) { 437 resizeHandlerRegistrater = Window.addResizeHandler(resizeHandler); 438 } 439 } 440 441 class KSDialogResizeHandler implements ResizeHandler { 442 @SuppressWarnings("deprecation") 443 @Override 444 public void onResize(ResizeEvent event) { 445 DeferredCommand.addCommand(new Command() { 446 447 @Override 448 public void execute() { 449 resizeDialog(); 450 int left = (Window.getClientWidth() - getOffsetWidth()) >> 1; 451 int top = (Window.getClientHeight() - getOffsetHeight()) >> 1; 452 setPopupPosition(Math.max(Window.getScrollLeft() + left, 0), Math.max( 453 Window.getScrollTop() + top, 0)); 454 } 455 }); 456 } 457 } 458 459 460 461 }