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 }