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 }