com.vaadin.terminal.gwt.client.ui.VAccordion Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of vaadin Show documentation
Show all versions of vaadin Show documentation
Vaadin is a web application framework for Rich Internet Applications (RIA).
Vaadin enables easy development and maintenance of fast and secure rich web
applications with a stunning look and feel and a wide browser support.
It features a server-side architecture with the majority of the logic running
on the server. Ajax technology is used at the browser-side to ensure a rich
and interactive user experience.
/*
* Copyright 2011 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.terminal.gwt.client.ui;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.ComplexPanel;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.terminal.gwt.client.ApplicationConnection;
import com.vaadin.terminal.gwt.client.BrowserInfo;
import com.vaadin.terminal.gwt.client.ContainerResizedListener;
import com.vaadin.terminal.gwt.client.Paintable;
import com.vaadin.terminal.gwt.client.RenderInformation;
import com.vaadin.terminal.gwt.client.RenderSpace;
import com.vaadin.terminal.gwt.client.UIDL;
import com.vaadin.terminal.gwt.client.Util;
import com.vaadin.terminal.gwt.client.VCaption;
import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate.TouchScrollHandler;
public class VAccordion extends VTabsheetBase implements
ContainerResizedListener {
public static final String CLASSNAME = "v-accordion";
private Set paintables = new HashSet();
private String height;
private String width = "";
private HashMap lazyUpdateMap = new HashMap();
private RenderSpace renderSpace = new RenderSpace(0, 0, true);
private StackItem openTab = null;
private boolean rendering = false;
private int selectedUIDLItemIndex = -1;
private RenderInformation renderInformation = new RenderInformation();
private final TouchScrollHandler touchScrollHandler;
public VAccordion() {
super(CLASSNAME);
// IE6 needs this to calculate offsetHeight correctly
if (BrowserInfo.get().isIE6()) {
DOM.setStyleAttribute(getElement(), "zoom", "1");
}
touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this);
}
@Override
public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
rendering = true;
selectedUIDLItemIndex = -1;
super.updateFromUIDL(uidl, client);
/*
* Render content after all tabs have been created and we know how large
* the content area is
*/
if (selectedUIDLItemIndex >= 0) {
StackItem selectedItem = getStackItem(selectedUIDLItemIndex);
UIDL selectedTabUIDL = lazyUpdateMap.remove(selectedItem);
open(selectedUIDLItemIndex);
selectedItem.setContent(selectedTabUIDL);
} else if (!uidl.getBooleanAttribute("cached") && openTab != null) {
close(openTab);
}
iLayout();
// finally render possible hidden tabs
if (lazyUpdateMap.size() > 0) {
for (Iterator iterator = lazyUpdateMap.keySet().iterator(); iterator
.hasNext();) {
StackItem item = (StackItem) iterator.next();
item.setContent(lazyUpdateMap.get(item));
}
lazyUpdateMap.clear();
}
renderInformation.updateSize(getElement());
rendering = false;
}
@Override
protected void renderTab(UIDL tabUidl, int index, boolean selected,
boolean hidden) {
StackItem item;
int itemIndex;
if (getWidgetCount() <= index) {
// Create stackItem and render caption
item = new StackItem(tabUidl);
if (getWidgetCount() == 0) {
item.addStyleDependentName("first");
}
itemIndex = getWidgetCount();
add(item, getElement());
} else {
item = getStackItem(index);
item = moveStackItemIfNeeded(item, index, tabUidl);
itemIndex = index;
}
item.updateCaption(tabUidl);
item.setVisible(!hidden);
if (selected) {
selectedUIDLItemIndex = itemIndex;
}
if (tabUidl.getChildCount() > 0) {
lazyUpdateMap.put(item, tabUidl.getChildUIDL(0));
}
}
/**
* This method tries to find out if a tab has been rendered with a different
* index previously. If this is the case it re-orders the children so the
* same StackItem is used for rendering this time. E.g. if the first tab has
* been removed all tabs which contain cached content must be moved 1 step
* up to preserve the cached content.
*
* @param item
* @param newIndex
* @param tabUidl
* @return
*/
private StackItem moveStackItemIfNeeded(StackItem item, int newIndex,
UIDL tabUidl) {
UIDL tabContentUIDL = null;
Paintable tabContent = null;
if (tabUidl.getChildCount() > 0) {
tabContentUIDL = tabUidl.getChildUIDL(0);
tabContent = client.getPaintable(tabContentUIDL);
}
Widget itemWidget = item.getComponent();
if (tabContent != null) {
if (tabContent != itemWidget) {
/*
* This is not the same widget as before, find out if it has
* been moved
*/
int oldIndex = -1;
StackItem oldItem = null;
for (int i = 0; i < getWidgetCount(); i++) {
Widget w = getWidget(i);
oldItem = (StackItem) w;
if (tabContent == oldItem.getComponent()) {
oldIndex = i;
break;
}
}
if (oldIndex != -1 && oldIndex > newIndex) {
/*
* The tab has previously been rendered in another position
* so we must move the cached content to correct position.
* We move only items with oldIndex > newIndex to prevent
* moving items already rendered in this update. If for
* instance tabs 1,2,3 are removed and added as 3,2,1 we
* cannot re-use "1" when we get to the third tab.
*/
insert(oldItem, getElement(), newIndex, true);
return oldItem;
}
}
} else {
// Tab which has never been loaded. Must assure we use an empty
// StackItem
Widget oldWidget = item.getComponent();
if (oldWidget != null) {
item = new StackItem(tabUidl);
insert(item, getElement(), newIndex, true);
}
}
return item;
}
private void open(int itemIndex) {
StackItem item = (StackItem) getWidget(itemIndex);
boolean alreadyOpen = false;
if (openTab != null) {
if (openTab.isOpen()) {
if (openTab == item) {
alreadyOpen = true;
} else {
openTab.close();
}
}
}
if (!alreadyOpen) {
item.open();
activeTabIndex = itemIndex;
openTab = item;
}
// Update the size for the open tab
updateOpenTabSize();
}
private void close(StackItem item) {
if (!item.isOpen()) {
return;
}
item.close();
activeTabIndex = -1;
openTab = null;
}
@Override
protected void selectTab(final int index, final UIDL contentUidl) {
StackItem item = getStackItem(index);
if (index != activeTabIndex) {
open(index);
iLayout();
// TODO Check if this is needed
client.runDescendentsLayout(this);
}
item.setContent(contentUidl);
}
public void onSelectTab(StackItem item) {
final int index = getWidgetIndex(item);
if (index != activeTabIndex && !disabled && !readonly
&& !disabledTabKeys.contains(tabKeys.get(index))) {
addStyleDependentName("loading");
client.updateVariable(id, "selected", "" + tabKeys.get(index), true);
}
}
@Override
public void setWidth(String width) {
if (this.width.equals(width)) {
return;
}
Util.setWidthExcludingPaddingAndBorder(this, width, 2);
this.width = width;
if (!rendering) {
updateOpenTabSize();
if (isDynamicHeight()) {
Util.updateRelativeChildrenAndSendSizeUpdateEvent(client,
openTab, this);
updateOpenTabSize();
}
if (isDynamicHeight()) {
openTab.setHeightFromWidget();
}
iLayout();
}
}
@Override
public void setHeight(String height) {
Util.setHeightExcludingPaddingAndBorder(this, height, 2);
this.height = height;
if (!rendering) {
updateOpenTabSize();
}
}
/**
* Sets the size of the open tab
*/
private void updateOpenTabSize() {
if (openTab == null) {
renderSpace.setHeight(0);
renderSpace.setWidth(0);
return;
}
// WIDTH
if (!isDynamicWidth()) {
int w = getOffsetWidth();
openTab.setWidth(w);
renderSpace.setWidth(w);
} else {
renderSpace.setWidth(0);
}
// HEIGHT
if (!isDynamicHeight()) {
int usedPixels = 0;
for (Widget w : getChildren()) {
StackItem item = (StackItem) w;
if (item == openTab) {
usedPixels += item.getCaptionHeight();
} else {
// This includes the captionNode borders
usedPixels += item.getHeight();
}
}
int offsetHeight = getOffsetHeight();
int spaceForOpenItem = offsetHeight - usedPixels;
if (spaceForOpenItem < 0) {
spaceForOpenItem = 0;
}
renderSpace.setHeight(spaceForOpenItem);
openTab.setHeight(spaceForOpenItem);
} else {
renderSpace.setHeight(0);
openTab.setHeightFromWidget();
}
}
public void iLayout() {
if (openTab == null) {
return;
}
if (isDynamicWidth()) {
int maxWidth = 40;
for (Widget w : getChildren()) {
StackItem si = (StackItem) w;
int captionWidth = si.getCaptionWidth();
if (captionWidth > maxWidth) {
maxWidth = captionWidth;
}
}
int widgetWidth = openTab.getWidgetWidth();
if (widgetWidth > maxWidth) {
maxWidth = widgetWidth;
}
super.setWidth(maxWidth + "px");
openTab.setWidth(maxWidth);
}
Util.runWebkitOverflowAutoFix(openTab.getContainerElement());
}
/**
*
*/
protected class StackItem extends ComplexPanel implements ClickHandler {
public void setHeight(int height) {
if (height == -1) {
super.setHeight("");
DOM.setStyleAttribute(content, "height", "0px");
} else {
super.setHeight((height + getCaptionHeight()) + "px");
DOM.setStyleAttribute(content, "height", height + "px");
DOM.setStyleAttribute(content, "top", getCaptionHeight() + "px");
}
}
public Widget getComponent() {
if (getWidgetCount() < 2) {
return null;
}
return getWidget(1);
}
@Override
public void setVisible(boolean visible) {
super.setVisible(visible);
}
public void setHeightFromWidget() {
Widget paintable = getPaintable();
if (paintable == null) {
return;
}
int paintableHeight = (paintable).getElement().getOffsetHeight();
setHeight(paintableHeight);
}
/**
* Returns caption width including padding
*
* @return
*/
public int getCaptionWidth() {
if (caption == null) {
return 0;
}
int captionWidth = caption.getRequiredWidth();
int padding = Util.measureHorizontalPaddingAndBorder(
caption.getElement(), 18);
return captionWidth + padding;
}
public void setWidth(int width) {
if (width == -1) {
super.setWidth("");
} else {
super.setWidth(width + "px");
}
}
public int getHeight() {
return getOffsetHeight();
}
public int getCaptionHeight() {
return captionNode.getOffsetHeight();
}
private VCaption caption;
private boolean open = false;
private Element content = DOM.createDiv();
private Element captionNode = DOM.createDiv();
public StackItem(UIDL tabUidl) {
setElement(DOM.createDiv());
caption = new VCaption(null, client);
caption.addClickHandler(this);
if (BrowserInfo.get().isIE6()) {
DOM.setEventListener(captionNode, this);
DOM.sinkEvents(captionNode, Event.BUTTON_LEFT);
}
super.add(caption, captionNode);
DOM.appendChild(captionNode, caption.getElement());
DOM.appendChild(getElement(), captionNode);
DOM.appendChild(getElement(), content);
getElement().addClassName(CLASSNAME + "-item");
captionNode.addClassName(CLASSNAME + "-item-caption");
content.addClassName(CLASSNAME + "-item-content");
touchScrollHandler.addElement(getContainerElement());
close();
}
@Override
public void onBrowserEvent(Event event) {
onSelectTab(this);
}
public Element getContainerElement() {
return content;
}
public Widget getPaintable() {
if (getWidgetCount() > 1) {
return getWidget(1);
} else {
return null;
}
}
public void replacePaintable(Paintable newPntbl) {
if (getWidgetCount() > 1) {
client.unregisterPaintable((Paintable) getWidget(1));
paintables.remove(getWidget(1));
remove(1);
}
add((Widget) newPntbl, content);
paintables.add(newPntbl);
}
public void open() {
open = true;
DOM.setStyleAttribute(content, "top", getCaptionHeight() + "px");
DOM.setStyleAttribute(content, "left", "0px");
DOM.setStyleAttribute(content, "visibility", "");
addStyleDependentName("open");
}
public void hide() {
DOM.setStyleAttribute(content, "visibility", "hidden");
}
public void close() {
DOM.setStyleAttribute(content, "visibility", "hidden");
DOM.setStyleAttribute(content, "top", "-100000px");
DOM.setStyleAttribute(content, "left", "-100000px");
removeStyleDependentName("open");
setHeight(-1);
setWidth("");
if (BrowserInfo.get().isIE6()) {
// Work around for IE6 layouting problem #3359
getElement().getStyle().setProperty("zoom", "1");
}
open = false;
}
public boolean isOpen() {
return open;
}
public void setContent(UIDL contentUidl) {
final Paintable newPntbl = client.getPaintable(contentUidl);
if (getPaintable() == null) {
add((Widget) newPntbl, content);
paintables.add(newPntbl);
} else if (getPaintable() != newPntbl) {
replacePaintable(newPntbl);
}
newPntbl.updateFromUIDL(contentUidl, client);
if (contentUidl.getBooleanAttribute("cached")) {
/*
* The size of a cached, relative sized component must be
* updated to report correct size.
*/
client.handleComponentRelativeSize((Widget) newPntbl);
}
if (isOpen() && isDynamicHeight()) {
setHeightFromWidget();
}
}
public void onClick(ClickEvent event) {
onSelectTab(this);
}
public void updateCaption(UIDL uidl) {
caption.updateCaption(uidl);
}
public int getWidgetWidth() {
return DOM.getFirstChild(content).getOffsetWidth();
}
public boolean contains(Paintable p) {
return (getPaintable() == p);
}
public boolean isCaptionVisible() {
return caption.isVisible();
}
}
@Override
protected void clearPaintables() {
clear();
}
public boolean isDynamicHeight() {
return height == null || height.equals("");
}
public boolean isDynamicWidth() {
return width == null || width.equals("");
}
@Override
@SuppressWarnings("unchecked")
protected Iterator