package org.tessell.widgets;
import java.util.ArrayList;
import java.util.Iterator;
import org.tessell.gwt.dom.client.GwtElement;
import org.tessell.gwt.dom.client.IsElement;
import org.tessell.gwt.dom.client.IsStyle;
import org.tessell.util.ListDiff.ListLike;
import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.WidgetCollection;
/**
* A table that can add entire rows at a time.
*
* Most GWT tables are cell-focused. E.g. <code>setWidget(0, 0, widget)</code>.
*
* We want a table where we can add an entire row at a time, and it was easiest to just roll our own extending from
* {@link Panel}.
*/
public class RowTable extends Panel implements IsRowTable {
private final Element table;
private final Element head;
private final Element body;
// Mixed headers+rows for our iterator method
private final WidgetCollection widgets = new WidgetCollection(this);
// Just rows for the replaceRow index
private final ArrayList<Widget> rows = new ArrayList<Widget>();
public RowTable() {
// copy/paste from HTMLTable
table = DOM.createTable();
head = DOM.createTHead();
body = DOM.createTBody();
table.appendChild(head);
table.appendChild(body);
setElement(table);
}
@Override
public void addHeader(final IsWidget isWidget) {
addHeader(isWidget.asWidget());
}
/** Assumes {@code widget} is a table and appends any of its TRs to our own table's header. */
public void addHeader(final Widget widget) {
addTo(widget, head);
}
@Override
public void add(final IsWidget isWidget) {
addRow(isWidget); // assume they meant row, e.g. for using in bind logic
}
@Override
public void addRow(final IsWidget isWidget) {
addRow(isWidget.asWidget());
}
/** Assumes {@code row} is a table row and returns its index */
@Override
public int indexOfRow(IsWidget row) {
return rows.indexOf(row.asWidget());
}
/** Assumes {@code widget} is a table and appends any of its TRs to our own table's body. */
public void addRow(final Widget widget) {
addTo(widget, body);
rows.add(widget);
}
@Override
public void insertRow(final int index, final IsWidget isWidget) {
insertRow(index, isWidget.asWidget());
}
@Override
public ListLike<org.tessell.gwt.user.client.ui.IsWidget> getRowsPanel() {
return new ListLikeAdapter();
}
/** Assumes {@code widget} is a table and puts its first TR into row {@code i} of our own table's body. */
public void insertRow(final int i, final Widget newWidget) {
final Element newTr = findTr(newWidget.getElement());
assert newTr != null : "newWidget did not contain a TR";
newWidget.removeFromParent();
// logical
widgets.add(newWidget);
rows.add(i, newWidget);
// physical
if (i == 0) {
body.insertFirst(newTr);
} else {
body.insertAfter(newTr, findBodyTr(i - 1));
}
// adopt
adopt(newWidget);
}
/** Assumes {@code widget} is a table and puts its first TR into row {@code i} of our own table's body. */
public void replaceRow(final int i, final Widget newWidget) {
// This will logically and physically remove the old widget
removeRow(i);
insertRow(i, newWidget);
}
@Override
public void removeRow(final int i) {
remove(rows.get(i));
}
@Override
public boolean removeRow(final IsWidget view) {
return remove(view.asWidget());
}
@Override
public boolean remove(final Widget child) {
if (widgets.contains(child)) {
// orphan
orphan(child);
// physical
final int at = rows.indexOf(child);
if (at > -1) {
final Widget oldTable = rows.remove(at);
final Element tr = findBodyTr(at);
body.removeChild(tr);
// put the oldTr back where we got it from
findTBody(oldTable.getElement()).appendChild(tr);
}
// logical
widgets.remove(child);
return true;
}
return false;
}
@Override
public Iterator<Widget> iterator() {
return widgets.iterator();
}
private void addTo(final Widget newWidget, final Element element) {
Element newTr = findTr(newWidget.getElement());
assert newTr != null : "newWidget did not contain a TR";
newWidget.removeFromParent();
// Logical attach
widgets.add(newWidget);
// Physical attach (all TRs)
element.appendChild(newTr);
// Adopt
adopt(newWidget);
}
@Override
public void replaceRow(final int i, final IsWidget isWidget) {
replaceRow(i, isWidget.asWidget());
}
@Override
public int size() {
return rows.size();
}
@Override
public void clearRows() {
while (rows.size() > 0) {
removeRow(0);
}
}
@Override
public Widget asWidget() {
return this;
}
@Override
public IsStyle getStyle() {
return getIsElement().getStyle();
}
@Override
public IsElement getIsElement() {
return new GwtElement(getElement());
}
@Override
public org.tessell.gwt.user.client.ui.IsWidget getIsParent() {
return (org.tessell.gwt.user.client.ui.IsWidget) getParent();
}
private com.google.gwt.dom.client.Element findBodyTr(final int i) {
int j = 0;
Element tr = body.getFirstChildElement();
while (tr != null && j < i) {
tr = tr.getNextSiblingElement();
j++;
}
return tr;
}
private Element findTr(Element tableElement) {
Element current = tableElement;
while (current != null && !current.getTagName().equalsIgnoreCase("TR")) {
current = current.getFirstChildElement();
}
return current;
}
private Element findTBody(Element tableElement) {
Element current = tableElement;
while (current != null && !current.getTagName().equalsIgnoreCase("TBODY")) {
current = current.getFirstChildElement();
}
if (current == null) {
current = tableElement;
}
return current;
}
/** Facilitates binding ListProperties to our rows. */
private final class ListLikeAdapter implements ListLike<org.tessell.gwt.user.client.ui.IsWidget> {
@Override
public org.tessell.gwt.user.client.ui.IsWidget remove(int index) {
Widget row = rows.get(index);
removeRow(index);
return (org.tessell.gwt.user.client.ui.IsWidget) row;
}
@Override
public void add(int index, org.tessell.gwt.user.client.ui.IsWidget a) {
insertRow(index, a);
}
}
}