
org.gwt.advanced.client.ui.widget.AdvancedFlexTable Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of advanced-gwt Show documentation
Show all versions of advanced-gwt Show documentation
The set of useful GWT components
/*
* Copyright 2008-2012 Sergey Skladchikov
*
* 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 org.gwt.advanced.client.ui.widget;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;
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.Timer;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.Widget;
import org.gwt.advanced.client.ui.SourcesTableDoubleClickEvents;
import org.gwt.advanced.client.ui.TableDoubleClickListener;
import org.gwt.advanced.client.ui.TableDoubleClickListenerCollection;
import org.gwt.advanced.client.util.GWTUtil;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* This is an advanced flex table.
* The difference with the original GWT flex table is that this component allows using <thead>
* and <th> HTML elements. It also allows enable vetical scrolling with fixed headers.
* For horizontal scrolling use traditional tools like ScrollPanel
.
*
* @author Sergey Skladchikov
* @since 1.0.0
*/
public class AdvancedFlexTable extends FlexTable implements SourcesTableDoubleClickEvents {
/**
* maximal timeout between two clicks in double click
*/
private static final int CLICK_TIMEOUT = 300;
/**
* the thead element
*/
private Element tHeadElement;
/**
* the tfoot element
*/
private Element tFootElement;
/**
* header widgets list
*/
private List headerWidgets;
/**
* footer widgets list
*/
private List footerWidgets;
/**
* a scroll panel widget (supported by IE only)
*/
private Panel scrollPanel;
/**
* a scrollable flag value
*/
private boolean scrollable;
/**
* list of table click handlers
*/
@SuppressWarnings({"deprecation"})
private HandlerManager handlerManager = new HandlerManager(this);
/**
* list of double click listeners registered in this widget
*/
private TableDoubleClickListenerCollection doubleClikcListeners = new TableDoubleClickListenerCollection();
/**
* the timer that is activated if the second click hasn't been done
*/
private Timer clickTimer = new ClickTimer();
/**
* count of clicks
*/
private int clickCount;
/**
* latest cell clicked
*/
private Element cell;
/**
* flag meaning that ONCLICK event is already sank
*/
private boolean clickEnabled;
/**
* enables double clicks, must be switched to false
if there are no double click listeners
*/
private boolean doubleClickEnabled;
/**
* Creates an instance of this class.
*/
public AdvancedFlexTable() {
}
/**
* Creates an instance of this class and initializes the THEAD element if the flag is true
.
* Otheriwse initialization happens only if the first header widget is added.
*
* @param initializeThead is an initilization flag.
*/
public AdvancedFlexTable(boolean initializeThead) {
if (initializeThead) {
tHeadElement = DOM.createTHead();
tHeadElement = DOM.createElement("thead");
DOM.insertChild(getElement(), getTHeadElement(), 0);
Element tr = DOM.createTR();
DOM.insertChild(getTHeadElement(), tr, 0);
}
}
/**
* This method sets a widget for the specified header cell.
*
* @param column is a column number.
* @param widget is a widget to be added to the cell.
*/
public void setHeaderWidget(int column, Widget widget) {
prepareHeaderCell(column);
if (widget != null) {
widget.removeFromParent();
Element th = DOM.getChild(DOM.getFirstChild(getTHeadElement()), column);
internalClearCell(th, true);
// Physical attach.
DOM.appendChild(th, widget.getElement());
List headerWidgets = getHeaderWidgets();
if (headerWidgets.size() > column && headerWidgets.get(column) != null)
headerWidgets.set(column, widget);
else
headerWidgets.add(column, widget);
adopt(widget);
}
}
/**
* This method removes the header widget.
*
* @param column is a column number.
*/
public void removeHeaderWidget(int column) {
if (column < 0)
throw new IndexOutOfBoundsException("Column number mustn't be negative");
Element tr = DOM.getFirstChild(getTHeadElement());
Element th = DOM.getChild(tr, column);
DOM.removeChild(tr, th);
getHeaderWidgets().remove(column);
}
/**
* This method sets a widget for the specified footer cell.
*
* @param column is a column number.
* @param widget is a widget to be added to the cell.
*/
public void setFooterWidget(int column, Widget widget) {
prepareFooterCell(column);
if (widget != null) {
widget.removeFromParent();
Element td = DOM.getChild(DOM.getFirstChild(getTFootElement()), column);
internalClearCell(td, true);
// Physical attach.
DOM.appendChild(td, widget.getElement());
List footerWidgets = getFooterWidgets();
if (footerWidgets.size() > column && footerWidgets.get(column) != null)
footerWidgets.set(column, widget);
else
footerWidgets.add(column, widget);
adopt(widget);
}
}
/**
* This method removes the header widget.
*
* @param column is a column number.
*/
public void removeFooterWidget(int column) {
if (column < 0)
throw new IndexOutOfBoundsException("Column number mustn't be negative");
Element tr = DOM.getFirstChild(getTFootElement());
Element th = DOM.getChild(tr, column);
DOM.removeChild(tr, th);
getFooterWidgets().remove(column);
}
/**
* This method enables vertical scrolling ability
* Note that in different browsers this feature can work in absolutely different ways.
* Remember this fact every time when you make CSS for your site.
*
* @param enabled if true
then the scrolling feature should be enabled,
*/
public void enableVerticalScrolling(boolean enabled) {
prepareScrolling(enabled);
setScrollable(enabled);
}
/**
* {@inheritDoc}
*/
public Iterator iterator() {
List notAttachedWidgets = new ArrayList();
for (Widget widget : getHeaderWidgets()) {
if (!widget.isAttached())
notAttachedWidgets.add(widget);
}
return new AdvancedWidgetIterator(super.iterator(), notAttachedWidgets.iterator());
}
/**
* Inserts a header cell element.
*
* @param column is a column number that the element will have.
*/
public void insertHeaderCell(int column) {
Element tr;
if (tHeadElement == null) {
tHeadElement = DOM.createElement("thead");
DOM.insertChild(getElement(), getTHeadElement(), 0);
tr = DOM.createTR();
DOM.insertChild(getTHeadElement(), tr, 0);
} else {
tr = DOM.getChild(tHeadElement, 0);
}
Element th = DOM.createTH();
DOM.insertBefore(tr, th, DOM.getChild(tr, column));
}
public void onBrowserEvent(Event event) {
if (event.getTypeInt() == Event.ONCLICK) {
setCellClicked(DOM.eventGetTarget(event));
if (getClickCount() % 2 == 0 && doubleClickEnabled) {
setClickCount(getClickCount() + 1);
getClickTimer().schedule(CLICK_TIMEOUT);
} else if (getClickCount() % 2 == 0 && !doubleClickEnabled) {
fireClickEvent();
} else if (doubleClickEnabled) {
setClickCount(0);
fireDoubleClickEvent();
getClickTimer().cancel();
}
}
super.onBrowserEvent(event);
}
/**
* Adds a table handler and sinks the ONCLICK event if it's not sank.
*
* @param handler is a handler to add.
*/
public HandlerRegistration addClickHandler(ClickHandler handler) {
HandlerRegistration registration = getHandlerManager().addHandler(ClickEvent.getType(), handler);
if (!clickEnabled) {
DOM.sinkEvents(getElement(), Event.ONCLICK);
clickEnabled = true;
}
return registration;
}
/**
* Adds a double click listener and sinks the ONCLICK event if it's not sank.
*
* @param listener is a listener to register.
*/
public void addDoubleClickListener(TableDoubleClickListener listener) {
removeDoubleClickListener(listener);
getDoubleClickListeners().add(listener);
if (!clickEnabled) {
DOM.sinkEvents(getElement(), Event.ONCLICK);
clickEnabled = true;
}
doubleClickEnabled = true;
}
/**
* Removes the double click listener.
*
* @param listener is a listener to remove.
*/
public void removeDoubleClickListener(TableDoubleClickListener listener) {
getDoubleClickListeners().remove(listener);
if (getDoubleClickListeners().isEmpty()) {
doubleClickEnabled = false;
}
}
/**
* Fires click events.
*/
protected void fireClickEvent() {
getHandlerManager().fireEvent(new ClickEvent(){});
setCellClicked(null);
}
@Override
public Cell getCellForEvent(ClickEvent event) {
Element td = getCellElement(getCellClicked());
if (td == null)
return null;
Element tr = DOM.getParent(td);
Element tbody = DOM.getParent(tr);
return new Cell(DOM.getChildIndex(tbody, tr), DOM.getChildIndex(tr, td)){};
}
/**
* Fires double click events.
*/
protected void fireDoubleClickEvent() {
Element td = getCellElement(getCellClicked());
if (td == null)
return;
Element tr = DOM.getParent(td);
Element tbody = DOM.getParent(tr);
getDoubleClickListeners().fireCellDoubleClicked(this, DOM.getChildIndex(tbody, tr), DOM.getChildIndex(tr, td));
setCellClicked(null);
}
/**
* Searches for the td element strting from the clicked element to upper levels of the DOM tree.
*
* @param clickElement is an element that is clicked.
* @return a found element or null
if the clicked element is not the td tag and not nested
* into any td.
*/
protected Element getCellElement(Element clickElement) {
while (clickElement != null && !"td".equalsIgnoreCase(clickElement.getTagName()))
clickElement = DOM.getParent(clickElement);
if (clickElement == null)
return null;
Element tr = DOM.getParent(clickElement);
Element tbody = DOM.getParent(tr);
Element table = DOM.getParent(tbody);
if (getElement().equals(table))
return clickElement;
else
return getCellElement(table);
}
@SuppressWarnings({"deprecation"})
protected HandlerManager getHandlerManager() {
return handlerManager;
}
@SuppressWarnings({"deprecation"})
protected void setHandlerManager(HandlerManager handlerManager) {
this.handlerManager = handlerManager;
}
protected TableDoubleClickListenerCollection getDoubleClickListeners() {
return doubleClikcListeners;
}
protected void setDoubleClikcListeners(TableDoubleClickListenerCollection doubleClikcListeners) {
this.doubleClikcListeners = doubleClikcListeners;
}
protected Timer getClickTimer() {
return clickTimer;
}
protected void setClickTimer(Timer clickTimer) {
this.clickTimer = clickTimer;
}
protected int getClickCount() {
return clickCount;
}
protected void setClickCount(int clickCount) {
this.clickCount = clickCount;
}
protected Element getCellClicked() {
return cell;
}
protected void setCellClicked(Element cell) {
this.cell = cell;
}
/**
* Prepares the flex table for scrolling.
* Currently this method supports IE6+ and Firefox 2.x
*
* @param enabled if true
then scrolling must be enabled.
*/
protected void prepareScrolling(boolean enabled) {
if (GWTUtil.isIE()) {
Panel scrollPanel = getScrollPanel();
if (enabled && !isScrollable()) {
Element parent = DOM.getParent(getElement());
String height = String.valueOf(getTableHeight());
String width = String.valueOf(getTableWidth());
if (parent != null) {
DOM.removeChild(parent, getElement());
DOM.appendChild(parent, scrollPanel.getElement());
}
DOM.appendChild(scrollPanel.getElement(), getElement());
scrollPanel.setHeight(height);
scrollPanel.setWidth(width);
} else if (!enabled && isScrollable()) {
Element parent = DOM.getParent(scrollPanel.getElement());
DOM.removeChild(scrollPanel.getElement(), getElement());
if (parent != null) {
DOM.removeChild(parent, scrollPanel.getElement());
DOM.appendChild(parent, getElement());
}
}
} else {
int tableHeight = getTableHeight();
int bodyHeight;
if (getTHeadElement() != null) {
bodyHeight = tableHeight - (DOM.getAbsoluteTop(getBodyElement()) - DOM.getAbsoluteTop(getTHeadElement()));
} else
bodyHeight = tableHeight;
if (enabled) {
DOM.setStyleAttribute(getBodyElement(), "height", String.valueOf(bodyHeight));
DOM.setStyleAttribute(getBodyElement(), "overflowY", "auto");
DOM.setStyleAttribute(getBodyElement(), "overflowX", "hidden");
} else {
DOM.setStyleAttribute(getBodyElement(), "overflowY", "visible");
DOM.setStyleAttribute(getBodyElement(), "overflowX", "visible");
}
}
}
/**
* This method returns an actual table height.
* If the value is not specified in the element styles, it returns the offset height.
*
* @return an actual table height.
*/
protected int getTableHeight() {
String height = DOM.getStyleAttribute(getElement(), "height");
if (height != null && height.endsWith("px")) {
return Integer.parseInt(height.substring(0, height.indexOf("px")));
} else {
return getOffsetHeight();
}
}
/**
* This method returns an actual table width.
* If the value is not specified in the element styles, it returns the offset width.
*
* @return an actual table width.
*/
protected int getTableWidth() {
String width = DOM.getStyleAttribute(getElement(), "width");
if (width != null && width.endsWith("px")) {
return Integer.parseInt(width.substring(0, width.indexOf("px"))) + 20;
} else {
return getOffsetWidth() + 20;
}
}
/**
* This method prepares the header cell to be used.
*
* @param column is a column number.
*/
protected void prepareHeaderCell(int column) {
if (column < 0) {
throw new IndexOutOfBoundsException(
"Cannot create a column with a negative index: " + column
);
}
if (tHeadElement == null) {
tHeadElement = DOM.createElement("thead");
DOM.insertChild(getElement(), getTHeadElement(), 0);
Element tr = DOM.createTR();
DOM.insertChild(getTHeadElement(), tr, 0);
}
List headerWidgets = getHeaderWidgets();
if (headerWidgets.size() <= column || headerWidgets.get(column) == null) {
int required = column + 1 - DOM.getChildCount(DOM.getChild(getTHeadElement(), 0));
if (required > 0)
addHeaderCells(getTHeadElement(), required);
}
}
/**
* This method prepares the footer cell to be used.
*
* @param column is a column number.
*/
protected void prepareFooterCell(int column) {
if (column < 0) {
throw new IndexOutOfBoundsException(
"Cannot create a column with a negative index: " + column
);
}
if (tFootElement == null) {
tFootElement = DOM.createElement("tfoot");
DOM.insertChild(getElement(), getTFootElement(), 1);
Element tr = DOM.createTR();
DOM.insertChild(getTFootElement(), tr, 0);
}
List footerWidgets = getFooterWidgets();
if (footerWidgets.size() <= column || footerWidgets.get(column) == null) {
int required = column + 1 - DOM.getChildCount(DOM.getChild(getTFootElement(), 0));
if (required > 0)
addFooterCells(getTFootElement(), required);
}
}
/**
* This native method is used to create TH tags instead of TD tags.
*
* @param tHead is a grid thead element.
* @param num is a number of columns to create.
*/
protected native void addHeaderCells(Element tHead, int num)/*-{
var rowElem = tHead.rows[0];
for(var i = 0; i < num; i++){
var cell = $doc.createElement("th");
rowElem.appendChild(cell);
}
}-*/;
/**
* This native method is used to create TD tags.
*
* @param tFoot is a grid tfoot element.
* @param num is a number of columns to create.
*/
protected native void addFooterCells(Element tFoot, int num)/*-{
var rowElem = tFoot.rows[0];
for(var i = 0; i < num; i++){
var cell = $doc.createElement("td");
rowElem.appendChild(cell);
}
}-*/;
/**
* Getter for property 'tHeadElement'.
*
* @return Value for property 'tHeadElement'.
*/
public Element getTHeadElement() {
return tHeadElement;
}
/**
* Getter for property 'tFootElement'.
*
* @return Value for property 'tFootElement'.
*/
public Element getTFootElement() {
return tFootElement;
}
/**
* Getter for property 'headerWidgets'.
*
* @return Value for property 'headerWidgets'.
*/
protected List getHeaderWidgets() {
if (headerWidgets == null)
headerWidgets = new ArrayList();
return headerWidgets;
}
/**
* Getter for property 'footerWidgets'.
*
* @return Value for property 'footerWidgets'.
*/
protected List getFooterWidgets() {
if (footerWidgets == null)
footerWidgets = new ArrayList();
return footerWidgets;
}
/**
* Getter for property 'scrollPanel'.
*
* @return Value for property 'scrollPanel'.
*/
protected Panel getScrollPanel() {
if (scrollPanel == null) {
scrollPanel = new RowsScrollPanel();
scrollPanel.setHeight(getOffsetHeight() + "px");
((ScrollPanel) scrollPanel).setAlwaysShowScrollBars(false);
}
return scrollPanel;
}
/**
* Setter for property 'scrollable'.
*
* @param scrollable Value to set for property 'scrollable'.
*/
protected void setScrollable(boolean scrollable) {
this.scrollable = scrollable;
}
/**
* Getter for property 'scrollable'.
*
* @return Value for property 'scrollable'.
*/
protected boolean isScrollable() {
return scrollable;
}
/**
* Overrides this method to make it accessible for this package and server side renderers.
*
* @return a body element.
*/
protected Element getBodyElement() {
return super.getBodyElement();
}
/**
* {@inheritDoc}
*/
protected void prepareCell(int row, int column) {
super.prepareCell(row, column);
}
/**
* {@inheritDoc}
*/
protected void prepareRow(int row) {
super.prepareRow(row);
}
/**
* {@inheritDoc}
*/
protected void checkCellBounds(int row, int column) {
super.checkCellBounds(row, column);
}
/**
* {@inheritDoc}
*/
protected void checkRowBounds(int row) {
super.checkRowBounds(row);
}
/**
* {@inheritDoc}
*/
protected Element createCell() {
return super.createCell();
}
/**
* {@inheritDoc}
*/
protected int getDOMCellCount(Element tableBody, int row) {
return super.getDOMCellCount(tableBody, row);
}
/**
* {@inheritDoc}
*/
protected int getDOMCellCount(int row) {
return super.getDOMCellCount(row);
}
/**
* {@inheritDoc}
*/
protected int getDOMRowCount() {
return super.getDOMRowCount();
}
/**
* {@inheritDoc}
*/
protected int getDOMRowCount(Element elem) {
return super.getDOMRowCount(elem);
}
/**
* {@inheritDoc}
*/
protected Element getEventTargetCell(Event event) {
return super.getEventTargetCell(event);
}
/**
* {@inheritDoc}
*/
protected void insertCells(int row, int column, int count) {
super.insertCells(row, column, count);
}
/**
* {@inheritDoc}
*/
protected boolean internalClearCell(Element td, boolean clearInnerHTML) {
return super.internalClearCell(td, clearInnerHTML);
}
/**
* {@inheritDoc}
*/
protected void prepareColumn(int column) {
super.prepareColumn(column);
}
/**
* {@inheritDoc}
*/
protected void setCellFormatter(CellFormatter cellFormatter) {
super.setCellFormatter(cellFormatter);
}
/**
* {@inheritDoc}
*/
protected void setColumnFormatter(ColumnFormatter formatter) {
super.setColumnFormatter(formatter);
}
/**
* {@inheritDoc}
*/
protected void setRowFormatter(RowFormatter rowFormatter) {
super.setRowFormatter(rowFormatter);
}
/**
* This is a scroll panel extension designed especially for rows scrolling.
*/
protected class RowsScrollPanel extends ScrollPanel {
/**
* Constructs a new RowsScrollPanel.
*/
public RowsScrollPanel() {
setStyleAttribute("position", "relative");
new Timer() {
public void run() {
if (getTHeadElement() == null)
return;
String top = DOM.getStyleAttribute(DOM.getChild(DOM.getChild(getTHeadElement(), 0), 0), "top");
if (!(getScrollPosition() + "px").equals(top))
setStyleAttribute("top", String.valueOf(getScrollPosition()));
}
}.scheduleRepeating(100); //this timer ensures that the header will always be on top
//whereas events don't
}
/**
* This method sets the specified style attribute to all the header rows.
*
* @param name is a name of style attribute.
* @param value is a value of style attribute.
*/
protected void setStyleAttribute(String name, String value) {
if (getTHeadElement() == null)
return;
int rowCount = DOM.getChildCount(getTHeadElement());
for (int i = 0; i < rowCount; i++)
DOM.setStyleAttribute(DOM.getChild(getTHeadElement(), i), name, value);
}
}
/**
* This is an implementation of widget iterator for the advanced table.
*
* @author Sergey Skladchikov
*/
protected class AdvancedWidgetIterator implements Iterator {
/**
* parent flex table iterator
*/
private Iterator parentIterator;
/**
* header widget collection iterator
*/
private Iterator headersIterator;
/**
* end of headers collection reached flag
*/
private boolean endOfHeadersReached;
/**
* Creates a new instance of this class.
*
* @param parentIterator is a parent flex table iterator.
* @param headerIterator is a header iterator.
*/
public AdvancedWidgetIterator(Iterator parentIterator, Iterator headerIterator) {
this.parentIterator = parentIterator;
this.headersIterator = headerIterator;
}
/**
* Returns true
if a least one of nested iterators returns true
.
*
* @return a value meaning that there is a least one widget exists.
*/
public boolean hasNext() {
return parentIterator.hasNext() || headersIterator.hasNext();
}
/**
* Returns the next widget attached to the table.
* Header widgets are followed by cell widgets.
*
* @return a next widget link.
*/
public Widget next() {
if (!headersIterator.hasNext()) {
endOfHeadersReached = true;
return parentIterator.next();
} else
return headersIterator.next();
}
/**
* Removes a currently selected widget.
*/
public void remove() {
if (!endOfHeadersReached)
headersIterator.remove();
else
parentIterator.remove();
}
}
/**
* This timer is invoked if the first click is received bu t the second one isn't till the
* {@link AdvancedFlexTable#CLICK_TIMEOUT} exceded.
* It drops clicks count and fires onclick event.
*/
protected class ClickTimer extends Timer {
/**
* See class docs
*/
public void run() {
setClickCount(0);
fireClickEvent();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy