001    /**
002     * Copyright 2010 The Kuali Foundation Licensed under the Educational Community License, Version 2.0 (the "License"); you may
003     * not use this file except in compliance with the License. You may obtain a copy of the License at
004     * http://www.osedu.org/licenses/ECL-2.0 Unless required by applicable law or agreed to in writing, software distributed
005     * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
006     * implied. See the License for the specific language governing permissions and limitations under the License.
007     */
008    
009    package org.kuali.student.common.ui.client.widgets.search;
010    
011    import java.util.ArrayList;
012    import java.util.List;
013    
014    import org.kuali.student.common.ui.client.reporting.ReportExportWidget;
015    import org.kuali.student.common.ui.client.theme.Theme;
016    import org.kuali.student.common.ui.client.util.ExportElement;
017    import org.kuali.student.common.ui.client.util.ExportUtils;
018    import org.kuali.student.common.ui.client.widgets.KSButton;
019    import org.kuali.student.common.ui.client.widgets.KSButtonAbstract.ButtonStyle;
020    import org.kuali.student.common.ui.client.widgets.field.layout.element.SpanPanel;
021    import org.kuali.student.common.ui.client.widgets.layout.HorizontalBlockFlowPanel;
022    import org.kuali.student.common.ui.client.widgets.layout.VerticalFlowPanel;
023    
024    import com.google.gwt.animation.client.Animation;
025    import com.google.gwt.event.dom.client.ClickEvent;
026    import com.google.gwt.event.dom.client.ClickHandler;
027    import com.google.gwt.user.client.DOM;
028    import com.google.gwt.user.client.ui.Composite;
029    import com.google.gwt.user.client.ui.DisclosurePanel;
030    import com.google.gwt.user.client.ui.Image;
031    import com.google.gwt.user.client.ui.SimplePanel;
032    import com.google.gwt.user.client.ui.Widget;
033    
034    public class CollapsablePanel extends Composite implements ReportExportWidget {
035        protected KSButton label;
036        protected VerticalFlowPanel layout = new VerticalFlowPanel();
037        protected HorizontalBlockFlowPanel linkPanel = new HorizontalBlockFlowPanel();
038        protected SimplePanel content = new SimplePanel();
039        protected ContentAnimation animation = new ContentAnimation();
040    
041        protected boolean isOpen = false;
042        protected boolean withImages = true;
043        protected ImagePosition imagePosition = ImagePosition.ALIGN_LEFT;
044    
045        public enum ImagePosition {
046            ALIGN_LEFT, ALIGN_RIGHT
047        }
048    
049        protected Image closedImage = Theme.INSTANCE.getCommonImages().getDisclosureClosedIcon();
050        protected Image openedImage = Theme.INSTANCE.getCommonImages().getDisclosureOpenedIcon();
051    
052        private ClickHandler openCloseClickHandler = new ClickHandler() {
053    
054            @Override
055            public void onClick(ClickEvent event) {
056                if (CollapsablePanel.this.isOpen) {
057                    CollapsablePanel.this.close();
058                } else {
059                    CollapsablePanel.this.open();
060                }
061            }
062        };
063    
064        private static class ContentAnimation extends Animation {
065            /**
066             * Whether the item is being opened or closed.
067             */
068            private boolean opening;
069    
070            /**
071             * The {@link DisclosurePanel} being affected.
072             */
073            private CollapsablePanel curPanel;
074    
075            /**
076             * Open or close the content.
077             * 
078             * @param panel
079             *            the panel to open or close
080             * @param animate
081             *            true to animate, false to open instantly
082             */
083            public void setOpen(CollapsablePanel panel, boolean animate) {
084                // Immediately complete previous open
085                cancel();
086    
087                // Open the new item
088                if (animate) {
089                    curPanel = panel;
090                    opening = panel.isOpen;
091                    run(1000);
092                } else {
093                    panel.content.setVisible(panel.isOpen);
094                    if (panel.isOpen) {
095                        // Special treatment on the visible case to ensure LazyPanel
096                        // works
097                        panel.content.setVisible(true);
098                    }
099                }
100            }
101    
102            @Override
103            protected void onComplete() {
104                if (!opening) {
105                    curPanel.content.setVisible(false);
106                }
107                DOM.setStyleAttribute(curPanel.content.getElement(), "height", "auto");
108                DOM.setStyleAttribute(curPanel.content.getElement(), "overflow", "visible");
109                curPanel = null;
110            }
111    
112            @Override
113            protected void onStart() {
114                super.onStart();
115                DOM.setStyleAttribute(curPanel.content.getElement(), "overflow", "hidden");
116                if (opening) {
117                    curPanel.content.setVisible(true);
118                    // Special treatment on the visible case to ensure LazyPanel works
119                    curPanel.content.setVisible(true);
120                }
121            }
122    
123            @Override
124            protected void onUpdate(double progress) {
125                int scrollHeight = DOM.getElementPropertyInt(curPanel.content.getElement(), "scrollHeight");
126                int height = (int) (progress * scrollHeight);
127                if (!opening) {
128                    height = scrollHeight - height;
129                }
130                height = Math.max(height, 1);
131    
132                DOM.setStyleAttribute(curPanel.content.getElement(), "height", height + "px");
133                DOM.setStyleAttribute(curPanel.content.getElement(), "width", "auto");
134            }
135        }
136    
137        protected CollapsablePanel() {}
138    
139        public CollapsablePanel(String label, Widget content, boolean isOpen) {
140            init(getButtonLabel(label), content, isOpen, true, ImagePosition.ALIGN_RIGHT);
141        }
142    
143        public CollapsablePanel(String label, Widget content, boolean isOpen, boolean withImages) {
144            init(getButtonLabel(label), content, isOpen, withImages, ImagePosition.ALIGN_RIGHT);
145        }
146    
147        public CollapsablePanel(String label, Widget content, boolean isOpen, boolean withImages, ImagePosition imagePosition) {
148            init(getButtonLabel(label), content, isOpen, withImages, imagePosition);
149        }
150    
151        public CollapsablePanel(Widget label, Widget content, boolean isOpen, boolean withImages, ImagePosition imagePosition) {
152            init(label, content, isOpen, withImages, imagePosition);
153        }
154    
155        protected void init(Widget label, Widget content, boolean isOpen, boolean withImages, ImagePosition imagePosition) {
156            this.isOpen = isOpen;
157            this.withImages = withImages;
158            this.imagePosition = imagePosition;
159            this.content.setWidget(content);
160    
161            if (this.imagePosition == ImagePosition.ALIGN_RIGHT) {
162                linkPanel.add(label);
163            }
164    
165            if (this.withImages) {
166                linkPanel.add(closedImage);
167                linkPanel.add(openedImage);
168                setImageState();
169            }
170    
171            if (this.imagePosition == ImagePosition.ALIGN_LEFT) {
172                linkPanel.add(label);
173            }
174    
175            if (!isOpen) {
176                this.content.setVisible(false);
177            }
178    
179            closedImage.addClickHandler(openCloseClickHandler);
180            openedImage.addClickHandler(openCloseClickHandler);
181    
182            layout.add(linkPanel);
183            layout.add(this.content);
184            closedImage.addStyleName("ks-image-middle-alignment");
185            openedImage.addStyleName("ks-image-middle-alignment");
186            content.addStyleName("top-padding");
187            this.initWidget(layout);
188        }
189    
190        protected KSButton getButtonLabel(String labelString) {
191            label = new KSButton(labelString, ButtonStyle.DEFAULT_ANCHOR);
192            label.addClickHandler(openCloseClickHandler);
193            return label;
194        }
195    
196        /**
197         * If the widget was initialized with a string label, it will return a KSButton. If the widget was initialized with a
198         * label widget, it will return the label widget.
199         * 
200         * @return
201         */
202        public KSButton getLabel() {
203            return label;
204        }
205    
206        public Widget getLabelWidget() {
207            return label;
208        }
209    
210        public boolean isOpen() {
211            return isOpen;
212        }
213    
214        public void open() {
215            isOpen = true;
216            if (withImages) {
217                setImageState();
218            }
219            animation.setOpen(this, true);
220        }
221    
222        public void close() {
223            isOpen = false;
224            if (withImages) {
225                setImageState();
226            }
227            animation.setOpen(this, true);
228        }
229    
230        /**
231         * Update the image state to display opened/closed image based in isOpen() status
232         */
233        protected void setImageState() {
234            closedImage.setVisible(!isOpen);
235            openedImage.setVisible(isOpen);
236        }
237    
238        @Override
239        public boolean isExportElement() {
240            return true;
241        }
242    
243        @Override
244        public List<ExportElement> getExportElementSubset(ExportElement parent) {
245            return ExportUtils.getDetailsForWidget(this.content.getWidget(), parent.getViewName(), parent.getSectionName());
246        }
247    
248        @Override
249        public String getExportFieldValue() {
250            String text = null;
251            for (int i = 0; i < this.linkPanel.getWidgetCount(); i++) {
252                Widget child = this.linkPanel.getWidget(i);
253                if (child instanceof SpanPanel) {
254                    SpanPanel header = (SpanPanel) child;
255                    text = header.getText();
256                }
257            }
258            return text;
259        }
260    
261    }