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    }