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
016package org.kuali.student.common.ui.client.widgets;
017
018import java.util.Arrays;
019import java.util.List;
020
021import com.google.gwt.dom.client.Element;
022import com.google.gwt.dom.client.NativeEvent;
023import com.google.gwt.dom.client.NodeList;
024import com.google.gwt.dom.client.Style.Overflow;
025import com.google.gwt.dom.client.Style.Unit;
026import com.google.gwt.event.dom.client.ClickEvent;
027import com.google.gwt.event.dom.client.ClickHandler;
028import com.google.gwt.event.dom.client.KeyCodes;
029import com.google.gwt.event.logical.shared.ResizeEvent;
030import com.google.gwt.event.logical.shared.ResizeHandler;
031import com.google.gwt.event.shared.HandlerRegistration;
032import com.google.gwt.user.client.Command;
033import com.google.gwt.user.client.DeferredCommand;
034import com.google.gwt.user.client.Event.NativePreviewEvent;
035import com.google.gwt.user.client.Window;
036import com.google.gwt.user.client.ui.Anchor;
037import com.google.gwt.user.client.ui.DialogBox;
038import com.google.gwt.user.client.ui.DockLayoutPanel;
039import com.google.gwt.user.client.ui.FlowPanel;
040import com.google.gwt.user.client.ui.ScrollPanel;
041import com.google.gwt.user.client.ui.SimplePanel;
042import 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 */
068public 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}