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 }