All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fr.lteconsulting.hexa.client.datatable.DataTable Maven / Gradle / Ivy

The newest version!
package fr.lteconsulting.hexa.client.datatable;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.core.shared.GWT;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Node;
import com.google.gwt.dom.client.SpanElement;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.TableLayout;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.dom.client.TableCellElement;
import com.google.gwt.dom.client.TableElement;
import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.dom.client.TableSectionElement;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.DoubleClickEvent;
import com.google.gwt.event.dom.client.DoubleClickHandler;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.EventListener;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Focusable;
import com.google.gwt.user.client.ui.ProvidesResize;
import com.google.gwt.user.client.ui.RequiresResize;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.impl.FocusImpl;

import fr.lteconsulting.hexa.client.css.HexaCss;
import fr.lteconsulting.hexa.client.tools.Action1;
import fr.lteconsulting.hexa.client.ui.containers.CustomPanel;
import fr.lteconsulting.hexa.client.ui.miracle.Size;

/**
 * A Table/Tree widget.
 * 
 * @author Arnaud
 */
public class DataTable extends Composite implements RequiresResize, ProvidesResize, Focusable//, HasKeyPressHandlers
{
	interface Css extends HexaCss
	{
		String main();
	}
	
	private static final Css CSS = GWT.create( Css.class );

	static final FocusImpl focusImpl = FocusImpl.getFocusImplForWidget();//Panel();
	private CustomPanel customPanel;
	private DivElement wrapper;
	private TableElement wrapperTable;
	private TableElement theadTable;
	private Element headTableColgroup;
	private TableElement table;
	private Element tableColgroup;
	private TableSectionElement thead;
	private TableRowElement theadtr;
	private TableSectionElement tbody;

	private RowImpl rootRow = new RowImpl( true );

	public DataTable()
	{
		// wrapper Div
		wrapper = Document.get().createDivElement();
		wrapper.getStyle().setPosition( Position.RELATIVE );
		wrapper.setTabIndex( 0 ); // for focusability

		// headings table
		theadTable = Document.get().createTableElement();
		theadTable.setClassName( "table" );
		theadTable.getStyle().setWidth( 100, Unit.PCT );
		theadTable.getStyle().setTableLayout( TableLayout.FIXED );
		
		headTableColgroup = Document.get().createElement( "colgroup" );
		
		// headings elements
		thead = Document.get().createTHeadElement();
		theadtr = Document.get().createTRElement();

		// main table
		table = Document.get().createTableElement();
		table.setClassName( "table" );
		table.getStyle().setWidth( 100, Unit.PCT );
		table.getStyle().setTableLayout( TableLayout.FIXED );
		
		tableColgroup = Document.get().createElement( "colgroup" );

		// table body
		tbody = Document.get().createTBodyElement();

		// wrapper table
		wrapperTable = Document.get().createTableElement();
		wrapperTable.getStyle().setTableLayout( TableLayout.FIXED );
		wrapperTable.getStyle().setWidth( 100, Unit.PCT );
		wrapperTable.getStyle().setHeight( 100, Unit.PCT );
		TableRowElement tr0 = Document.get().createTRElement();
		TableCellElement td0 = Document.get().createTDElement();
		DivElement div0 = Document.get().createDivElement();
		div0.getStyle().setWidth( 100, Unit.PCT );
		TableRowElement tr1 = Document.get().createTRElement();
		tr1.getStyle().setHeight( 100, Unit.PCT );
		TableCellElement td1 = Document.get().createTDElement();
		DivElement div1 = Document.get().createDivElement();
		div1.getStyle().setHeight( 100, Unit.PCT );
		div1.getStyle().setWidth( 100, Unit.PCT );
		div1.getStyle().setPosition( Position.RELATIVE );
		DivElement div2 = Document.get().createDivElement();
		div2.getStyle().setPosition( Position.ABSOLUTE );
		div2.getStyle().setTop( 0, Unit.PX );
		div2.getStyle().setLeft( 0, Unit.PX );
		div2.getStyle().setRight( 0, Unit.PX );
		div2.getStyle().setBottom( 0, Unit.PX );
		div2.getStyle().setOverflow( Overflow.AUTO );

		// bind together
		theadTable.appendChild( headTableColgroup );
		theadTable.appendChild( thead. cast() );
		thead.appendChild( theadtr. cast() );
		table.appendChild( tableColgroup );
		table.appendChild( tbody. cast() );
		td0.appendChild( div0 );
		div0.appendChild( theadTable. cast() );
		div1.appendChild( div2 );
		div2.appendChild( table );
		td1.appendChild( div1. cast() );
		tr0.appendChild( td0 );
		tr1.appendChild( td1 );
		wrapperTable.appendChild( tr0 );
		wrapperTable.appendChild( tr1 );
		wrapper.appendChild( wrapperTable );

		customPanel = new CustomPanel( wrapper );
		initWidget( customPanel );

		setStylePrimaryName( CSS.main() );
	}

	public int addColumn( String html, String width )
	{
		headTableColgroup.appendChild( createColumnElement( width ) );
		tableColgroup.appendChild( createColumnElement( width ) );

		TableCellElement th = Document.get().createTHElement();
		th.setInnerHTML( html );

		thead.appendChild( th );

		int index = getNbColumns() - 1;

		return index;
	}
	
	public Element getColumnElement( int index )
	{
		if( index < 0 || index >= getNbColumns() )
			return null;
		
		return tableColgroup.getChild( index ).cast();
	}

	private Element createColumnElement( String size )
	{
		Element col = Document.get().createElement( "col" );
		if( size != null )
			col.getStyle().setProperty( "width", size );
		else
			col.getStyle().clearProperty( "width" );
		return col;
	}

	private class RowImpl implements Row
	{
		private final TableRowElement tr;
		RowImpl parent;

		ArrayList childs;

		ArrayList cells = new ArrayList<>();

		public RowImpl()
		{
			this( false );
		}

		public RowImpl( boolean isRoot )
		{
			if( isRoot )
			{
				tr = null;
			}
			else
			{
				tr = Document.get().createTRElement();

				tr.setPropertyObject( "rowimpl", this );

				int nb = getNbColumns();
				StringBuilder sb = new StringBuilder();
				for( int i = 0; i < nb; i++ )
					sb.append( "" );

				tr.setInnerHTML( sb.toString() );
			}
		}
		
		private boolean isOpened()
		{
			if( tr == null )
				return true;
			
			String state = tr.getAttribute( "open" );
			return state == null || state.isEmpty();
		}

		private void detach()
		{
			if( tr != null )
			{
				if( tr.getParentElement() != null )
					tr.removeFromParent();
			}

			if( parent != null )
			{
				parent.childs.remove( this );
				parent = null;
			}
		}

		/**
		 * Create a child row
		 */
		@Override
		public Row addRow()
		{
			RowImpl child = new RowImpl();

			insertLastChild( child );
			
			updateTreeCell();

			return child;
		}
		
		@Override
		public Row insertRowAt( int position )
		{
			RowImpl child = new RowImpl();

			insertChildAt( child, position );
			
			updateTreeCell();
			
			return child;
		}

		@Override
		public void acceptAsLastChild( Row row )
		{
			if( row == null )
				return;

			if( !(row instanceof RowImpl) )
				throw new RuntimeException( "Row cannot be accept as a child, incompatible implementation." );

			RowImpl child = (RowImpl) row;

			insertLastChild( child );
		}
		
		@Override
		public void acceptAsNthChild( Row row, int position )
		{
			if( row == null )
				return;

			if( !(row instanceof RowImpl) )
				throw new RuntimeException( "Row cannot be accept as a child, incompatible implementation." );

			RowImpl child = (RowImpl) row;

			insertChildAt( child, position );
		}
		
		@Override
		public boolean isFolded()
		{
			if( ! hasChildren() )
				return false;
			return ! isOpened();
		}
		
		@Override
		public void setFolded( boolean isFolded )
		{
			setOpened( ! isFolded );
		}
		
		@Override
		public void toggleChildDisplay()
		{
			if( ! hasChildren() )
				return;
			
			final boolean isOpened = ! isOpened();
			
			setOpened( isOpened );
		}

		private void setOpened( final boolean isOpened )
		{
			if( isOpened )
				tr.removeAttribute( "open" );
			else
				tr.setAttribute( "open", "x" );
			
			updateTreeCell();
			
			visitDepthFirstPre( new AbstractVisitor()
			{
				@Override
				public void beginVisit( Row node )
				{
					if( node == RowImpl.this )
						return;
					
					if( ! isOpened )
					{
						node.getTr().getStyle().setDisplay( Display.NONE );
						return;
					}
					else
					{
						node.getTr().getStyle().clearDisplay();
					}
				}
			} );
		}

		@Override
		public RowImpl getParentRow()
		{
			return parent;
		}

		@SuppressWarnings( "unchecked" )
		@Override
		public List getChildrenRows()
		{
			return (List) (Object) childs;
		}

		@Override
		public Object visitDepthFirstPre( Visitor visitor )
		{
			visitor.beginVisit( this );

			if( hasChildren() )
			{
				for( Row child : getChildrenRows() )
				{
					visitor.beginVisitChild( this, child );
					
					Object childRes = ((RowImpl) child).visitDepthFirstPre( visitor );

					visitor.endVisitChild( this, child, childRes );
				}
			}

			return visitor.endVisit( this );
		}

		@Override
		public int getLevel()
		{
			if( parent == null )
				return 0;

			return 1 + parent.getLevel();
		}
		
		private void updateTreeCell()
		{
			if( cells!=null && (!cells.isEmpty()) && cells.get( 0 ) instanceof TreeCellImpl )
				((TreeCellImpl)cells.get( 0 )).updateTreeInfo();
		}

		/**
		 * Does both the logical and physical attachment of the child row
		 * 
		 * @param child
		 *            The row to be added at the end of the children collection
		 */
		private void insertLastChild( RowImpl child )
		{
			assert (child != null);

			child.detach();

			// do the DOM attach
			RowImpl lastChildRow = getLastChildDeep();
			tbody.insertAfter( child.tr, lastChildRow.tr );

			// do the logical attach (to the child list)
			child.parent = this;
			getChildren().add( child );

			// ensure child's descendants are at a good place
			class ReplacingVisitor implements Action1
			{
				private Element previousTr = null;

				@Override
				public void exec( RowImpl row )
				{
					TableCellElement td = row.tr.getChild( 0 ).cast();
					td.getStyle().setPaddingLeft( 10 * row.getLevel(), Unit.PX );

					if( previousTr == null )
					{
						previousTr = row.tr;
						return;
					}

					tbody.insertAfter( row.tr, previousTr );
					previousTr = row.tr;
				}
			}

			child.browseDeep( new ReplacingVisitor() );
		}
		
		private void insertChildAt( RowImpl child, int position )
		{
			assert (child != null);

			child.detach();

			// do the DOM attach
			if( position > 0 )
			{
				RowImpl lastChildRow = getChildren().get( position - 1 ).getLastChildDeep();
				tbody.insertAfter( child.tr, lastChildRow.tr );
			}
			else
			{
				if( tr != null )
					tbody.insertAfter( child.tr, tr );
				else
					tbody.insertFirst( child.tr );
			}
			
			// do the logical attach (to the child list)
			child.parent = this;
			getChildren().add( position, child );

			// ensure child's descendants are at a good place
			class ReplacingVisitor implements Action1
			{
				private Element previousTr = null;

				@Override
				public void exec( RowImpl row )
				{
					TableCellElement td = row.tr.getChild( 0 ).cast();
					td.getStyle().setPaddingLeft( 10 * row.getLevel(), Unit.PX );

					if( previousTr == null )
					{
						previousTr = row.tr;
						return;
					}

					tbody.insertAfter( row.tr, previousTr );
					previousTr = row.tr;
				}
			}

			child.browseDeep( new ReplacingVisitor() );
		}

		/**
		 * Visits this node and its child.
		 * 
		 * @param visitor
		 */
		private void browseDeep( Action1 visitor )
		{
			visitor.exec( this );

			if( !hasChildren() )
				return;
			for( RowImpl child : childs )
				child.browseDeep( visitor );
		}

		/**
		 * Returns the last deep child RowImp or this if there is
		 * no child
		 * 
		 * @return
		 */
		private RowImpl getLastChildDeep()
		{
			if( !hasChildren() )
				return this;
			return childs.get( childs.size() - 1 ).getLastChildDeep();
		}

		private RowImpl getNextSiblingRow()
		{
			if( parent == null )
				return null;

			int index = getRowIndex() + 1;
			if( index >= parent.childs.size() )
				return null;

			return parent.childs.get( index );
		}

		private RowImpl getPreviousSiblingRow()
		{
			if( parent == null )
				return null;

			int index = getRowIndex() - 1;
			if( index < 0 )
				return null;

			return parent.childs.get( index );
		}

		public boolean hasChildren()
		{
			return childs != null && !childs.isEmpty();
		}

		public ArrayList getChildren()
		{
			if( childs == null )
				childs = new ArrayList<>();
			return childs;
		}

		@Override
		public void remove()
		{
			tr.setPropertyObject( "rowimpl", null );

			RowImpl oldParent = parent;
			
			detach();
			
			if( oldParent != null )
				oldParent.updateTreeCell();
		}

		@Override
		public Cell getCell( int column )
		{
			ensureEnoughCells( column + 1 );

			return cells.get( column );
		}

		@Override
		public int getCellCount()
		{
			return cells.size();
		}

		@Override
		public Row getPreviousRow()
		{
			// previous sibling
			RowImpl sibling = getPreviousSiblingRow();
			if( sibling == null )
			{
				// go up to the parent
				if( parent == rootRow )
					return rootRow.getLastChildDeep();
				
				return parent;
			}
			
			RowImpl lastChild = sibling.getLastChildDeep();
			if( lastChild == null )
				return sibling;
			
			RowImpl closedOne = getFirstClosedAncestor( lastChild );
			return closedOne != null ? closedOne : lastChild;
		}
		
		private RowImpl getFirstClosedAncestor( RowImpl row )
		{
			RowImpl first = null;
			while( row != null )
			{
				if( ! row.isOpened() )
					first = row;
				
				row = row.getParentRow();
			}
			
			return first;
		}

		@Override
		public Row getNextRow()
		{
			// first child if any
			if( hasChildren() && isOpened() )
				return getChildren().get( 0 );
			
			// recursively go up to find the first next sibling of the first parent encountered
			RowImpl cur = this;
			while( cur != null )
			{
				RowImpl sibling = cur.getNextSiblingRow();
				if( sibling != null )
					return sibling;
				
				cur = cur.getParentRow();
			}
			
			return rootRow.getNextRow();
		}

		int getRowIndex()
		{
			return parent.childs.indexOf( this );
		}

		@Override
		public TableRowElement getTr()
		{
			return tr;
		}

		private void ensureEnoughCells( int nb )
		{
			if( cells.size() >= nb )
				return;

			for( int i = cells.size(); i < nb; i++ )
			{
				if( tr.getChildCount() < i + 1 )
				{
					TableCellElement td = Document.get().createTDElement();
					tr.appendChild( td );
				}

				CellImpl cell = i == 0 ? new TreeCellImpl() : new CellImpl();
				cells.add( cell );
			}
		}
		
		private class TreeCellImpl extends CellImpl implements EventListener
		{
			void updateTreeInfo()
			{
				if( getParentRow().hasChildren() )
				{
					if( getParentRow().isOpened() )
						getTd().getChild( 0 ).cast().setInnerText( "[-] " );
					else
						getTd().getChild( 0 ).cast().setInnerText( "[+] " );
				}
				else
				{
					getTd().getChild( 0 ).cast().setInnerText( "" );
				}
				
			}
			
			private void ensureTdOk()
			{
				TableCellElement td = getTd();
				if( td == null )
					return;
				
				int count = td.getChildCount();
				
				if( count <= 0 )
				{
					SpanElement treeView = Document.get().createSpanElement();
					td.appendChild( treeView );
					
					DOM.sinkEvents( treeView, Event.ONCLICK );
					DOM.setEventListener( treeView, this );
				}
				
				if( count <= 1 )
				{
					SpanElement content = Document.get().createSpanElement();
					td.appendChild( content );
				}
			}

			@Override
			public void onBrowserEvent( Event arg0 )
			{
				if( arg0.getTypeInt() == Event.ONCLICK )
				{
					RowImpl row = getParentRow();
					row.toggleChildDisplay();
				}
			}
			
			@Override
			public void setText( String text )
			{
				clearCellWidget();
				
				ensureTdOk();
				
				getTd().getChild( 1 ).cast().setInnerText( text );
			}

			@Override
			public void setHTML( String html )
			{
				clearCellWidget();
				
				ensureTdOk();
				
				getTd().getChild( 1 ).cast().setInnerHTML( html );
			}

			@Override
			public void setWidget( Widget widget )
			{
				clearCellWidget();
				
				ensureTdOk();

				getTd().getChild( 1 ).cast().setInnerText( "" );

				childWidget = widget;
				if( childWidget != null )
				{
					customPanel.addIn( getTd().getChild( 1 ).cast(), childWidget );
				}
			}
		}

		private class CellImpl implements Cell
		{
			Widget childWidget = null;

			@Override
			public RowImpl getParentRow()
			{
				return RowImpl.this;
			}

			@Override
			public int getCellIndex()
			{
				return cells.indexOf( this );
			}

			@Override
			public TableCellElement getTd()
			{
				TableRowElement tr = getTr();
				if( tr == null )
					return null;

				return tr.getChild( getCellIndex() ).cast();
			}

			@Override
			public void setText( String text )
			{
				clearCellWidget();

				TableCellElement td = getTd();
				if( td != null )
					td.setInnerText( text );
			}

			@Override
			public void setHTML( String html )
			{
				clearCellWidget();

				TableCellElement td = getTd();
				if( td != null )
					td.setInnerHTML( html );
			}

			@Override
			public void setWidget( Widget widget )
			{
				clearCellWidget();

				TableCellElement td = getTd();
				if( td != null )
				{
					td.setInnerText( "" );

					childWidget = widget;
					if( childWidget != null )
					{
						customPanel.addIn( td, childWidget );
					}
				}
			}

			@Override
			public void scrollIntoView()
			{
				TableCellElement td = getTd();
				if( td != null )
					td.scrollIntoView();
			}

			@Override
			public void addClassName( String className )
			{
				TableCellElement td = getTd();
				if( td != null )
					td.addClassName( className );
			}

			@Override
			public void removeClassName( String className )
			{
				TableCellElement td = getTd();
				if( td != null )
					td.removeClassName( className );
			}

			@Override
			public Cell getNextCell()
			{
				if( getCellIndex() < cells.size() - 1 )
					return cells.get( getCellIndex() + 1 );

				Row row = getNextRow();
				return row.getCell( 0 );
			}

			@Override
			public Cell getPreviousCell()
			{
				if( getCellIndex() > 0 )
					return cells.get( getCellIndex() - 1 );

				// last cell in the previous row
				Row row = getPreviousRow();
				return row.getCell( row.getCellCount() - 1 );
			}

			@Override
			public Size getDisplaySize()
			{
				Element td = getTd();
				Size size = new Size( td.getOffsetWidth(), td.getOffsetHeight() );
				return size;
			}

			protected void clearCellWidget()
			{
				if( childWidget != null )
				{
					customPanel.remove( childWidget );
					childWidget = null;
				}
			}
		}

		@Override
		public void addClassName( String className )
		{
			TableRowElement tr = getTr();
			if( tr != null )
				tr.addClassName( className );
		}

		@Override
		public void removeClassName( String className )
		{
			TableRowElement tr = getTr();
			if( tr != null )
				tr.removeClassName( className );
		}
	}

	public Row addRow()
	{
		return rootRow.addRow();
	}

	public int getNbColumns()
	{
		return tableColgroup.getChildCount();
	}

	public Row getRootRow()
	{
		return rootRow;
	}

	public Cell getCellForEvent( Event event )
	{
		Element td = getEventTargetCell( event );
		if( td == null )
			return null;

		Element tr = td.getParentElement();
		if( tr == null )
			return null;

		int column = DOM.getChildIndex( tr, td );

		RowImpl row = (RowImpl) tr.getPropertyObject( "rowimpl" );
		if( row == null )
			return null;

		return row.cells.get( column );
	}

	public Row getRowForElement( Element element )
	{
		Element tr = getEventTargetRow( element );
		if( tr == null )
			return null;

		RowImpl row = (RowImpl) tr.getPropertyObject( "rowimpl" );
		return row;
	}

	@Override
	public void onResize()
	{
	}

	@Override
	public void setAccessKey( char key )
	{
		focusImpl.setAccessKey( getElement(), key );
	}

	@Override
	public void setFocus( boolean focused )
	{
		if( focused )
		{
			focusImpl.focus( getElement() );
		}
		else
		{
			focusImpl.blur( getElement() );
		}
	}

	@Override
	public int getTabIndex()
	{
		return focusImpl.getTabIndex( getElement() );
	}

	@Override
	public void setTabIndex( int index )
	{
		focusImpl.setTabIndex( getElement(), index );
	}

	public HandlerRegistration addKeyDownHandler( final KeyDownHandler handler )
	{
		return addDomHandler( handler, KeyDownEvent.getType() );
	}

	public HandlerRegistration addKeyUpHandler( final KeyUpHandler handler )
	{
		return addDomHandler( handler, KeyUpEvent.getType() );
	}

	public HandlerRegistration addKeyPressHandler( final KeyPressHandler handler )
	{
		return addDomHandler( handler, KeyPressEvent.getType() );
	}

	public HandlerRegistration addCellClickHandler( final ClickHandler handler )
	{
		return addDomHandler( handler, ClickEvent.getType() );
	}

	public HandlerRegistration addCellDoubleClickHandler( final DoubleClickHandler handler )
	{
		return addDomHandler( handler, DoubleClickEvent.getType() );
	}

	public HandlerRegistration addCellMouseDownHandler( final MouseDownHandler handler )
	{
		return addDomHandler( handler, MouseDownEvent.getType() );
	}
	
	public HandlerRegistration addCellMouseUpHandler( final MouseUpHandler handler )
	{
		return addDomHandler( handler, MouseUpEvent.getType() );
	}

	protected Element getEventTargetCell( Event event )
	{
		Element me = getElement();

		Element td = DOM.eventGetTarget( event );
		for( ; td != null; td = DOM.getParent( td ) )
		{
			// If it's a TD, it might be the one we're looking for.
			if( td.getTagName().equalsIgnoreCase( "td" ) )
			{
				// Make sure it's directly a part of this table before returning
				// it
				Element tr = DOM.getParent( td );
				Element body = DOM.getParent( tr );
				if( body == tbody )
					return td;
			}

			// If we run into this widget's root element, we're out of options.
			if( td == me )
				return null;
		}

		return null;
	}
	
	protected Element getEventTargetRow( Element element )
	{
		Element me = getElement();

		for( ; element != null; element = DOM.getParent( element ) )
		{
			// If it's a TR, it might be the one we're looking for.
			if( element.getTagName().equalsIgnoreCase( "tr" ) )
			{
				// Make sure it's directly a part of this table before returning it
				Element body = DOM.getParent( element );
				if( body == tbody )
					return element;
			}

			// If we run into this widget's root element, we're out of options.
			if( element == me )
				return null;
		}

		return null;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy