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

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

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

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;

import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Unit;
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.KeyCodes;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.logical.shared.HasSelectionHandlers;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.event.shared.SimpleEventBus;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Widget;

import fr.lteconsulting.hexa.client.comm.HexaFramework;
import fr.lteconsulting.hexa.client.tools.Func1;
import fr.lteconsulting.hexa.client.ui.miracle.Printer;
import fr.lteconsulting.hexa.client.ui.miracle.Size;
import fr.lteconsulting.hexa.client.ui.tools.IColumn;
import fr.lteconsulting.hexa.client.ui.tools.IEditor;
import fr.lteconsulting.hexa.client.ui.tools.IEditorHost;

public class ObjectCollectionManager implements HasSelectionHandlers
{
	public interface Callback
	{
		void onWantDelete( T record );
	}
	
	public interface RowCustomizer
	{
		void onAfterPrint( Row row, T record );
	}
	
	private static class ColumnInfo
	{
		public ColumnInfo( IColumn column, String size )
		{
			this.column = column;
			this.size = size;
		}
		
		private final IColumn column;
		private final String size;
	}
	
	private final SimpleEventBus bus = new SimpleEventBus();
	
	private final DataTable table;
	private Callback callback;
	
	private final List> columns;
	private final List> customizers;
	
	private final boolean withDeleteColumn;
	
	DataTableCellSelection selection;
	T previouslySelectedRecord;
	
	HashMap rows = new HashMap<>();
	HashMap records = new HashMap<>();
	
	IEditor currentEditor;
	Cell editedCell;
	T editedRecord;
	
	/**
	 * If set, this hierarchy function allows to ask for the parent of a record
	 */
	Func1 hierarchyFunction;
	
	Comparator sortFunction;
	
	public ObjectCollectionManager( boolean withDeleteColumn )
	{
		this.withDeleteColumn = withDeleteColumn;
		
		columns = new ArrayList<>();
		customizers = new ArrayList<>();
		table = new DataTable();
	}
	
	public void setHierarchyFunction( Func1 hierarchyFunction )
	{
		this.hierarchyFunction = hierarchyFunction;
	}
	
	public void setSortFunction( Comparator comparator )
	{
		this.sortFunction = comparator;
	}
	
	public void addColumn( IColumn column, String size )
	{
		columns.add( new ColumnInfo( column, size ) );
	}
	
	public void addRowCustomizer( RowCustomizer customizer )
	{
		customizers.add( customizer );
	}
	
	public void init()
	{
		if( withDeleteColumn )
		{
			addColumn( new IColumn()
			{
				@Override
				public void fillCell( Printer printer, final T record )
				{
					printer.setHTML( "" );
				}
	
				@Override
				public String getTitle()
				{
					return "";
				}
	
				@Override
				public IEditor editCell( T record )
				{
					return null;
				}
			}, "40px" );
			
			final ColumnInfo newColumn = columns.get( columns.size() - 1 );
			
			// handles clicks on the last column (delete column)
			table.addCellClickHandler( new ClickHandler()
			{
				@Override
				public void onClick( ClickEvent event )
				{
					Cell cell = table.getCellForEvent( event.getNativeEvent().cast() );
					if( cell == null )
						return;
					
					// delete column is the last column
					if( cell.getCellIndex() != columns.indexOf( newColumn ) )
						return;
					
					T record = getRecordForRow( cell.getParentRow() );
					if( callback != null )
						callback.onWantDelete( record );
				}
			} );
		}
		
		for( ColumnInfo c : columns )
			table.addColumn( c.column.getTitle(), c.size );
		
		selection = new DataTableCellSelection( table );
		selection.init();
		selection.addSelectionHandler( new SelectionHandler()
		{
			@Override
			public void onSelection( SelectionEvent event )
			{
				cancelCurrentEdition();
				
				Cell cell = selection.getSelectedCell();
				if( cell == null )
					return;
				
				T record = getRecordForRow( cell.getParentRow() );
				
				if( previouslySelectedRecord != record )
					SelectionEvent.fire( ObjectCollectionManager.this, record );
				
				previouslySelectedRecord = record;
			}
		} );
		
		table.addKeyUpHandler( new KeyUpHandler()
		{
			@Override
			public void onKeyUp( KeyUpEvent event )
			{
				if( event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER
						|| event.getNativeEvent().getKeyCode() == KeyCodes.KEY_F2 )
				{
					editCell( selection.getSelectedCell() );
				}
			}
		} );
		
		table.addCellDoubleClickHandler( new DoubleClickHandler()
		{
			@Override
			public void onDoubleClick( DoubleClickEvent event )
			{
				editCell( selection.getSelectedCell() );
			}
		});
	}
	
	public void setCallback( Callback callback )
	{
		this.callback = callback;
	}
	
	public DataTable getTable()
	{
		return table;
	}
	
	public DataPlug getDataPlug()
	{
		return dataPlug;
	}
	
	protected void storeRow( T record, Row row )
	{
		rows.put( record, row );
		records.put( row, record );
	}
	
	protected void forgetRow( T record )
	{
		Row row = rows.remove( record );
		if( row != null )
			records.remove( row );
	}
	
	protected void forgetAllRows()
	{
		rows.clear();
		records.clear();
	}
	
	private void printRecordOnRow( T record, Row row )
	{
		if( row == null )
			return;
		
		int i = 0;
		for( ColumnInfo c : columns )
			c.column.fillCell( row.getCell( i++ ), record );
		
		for( RowCustomizer customizer : customizers )
			customizer.onAfterPrint( row, record );
	}
	
	/**
	 * Begin cell edition
	 * 
	 * @param cell
	 */
	private void editCell( Cell cell )
	{
		if( cell == null || cell == editedCell )
			return;
		
		T record = getRecordForRow( cell.getParentRow() );
		if( record == null )
			return;
		
		registerCurrentEdition( cell, record );
	}
	
	/**
	 * Gets the record associated with a specific Row
	 * 
	 * @param row The row
	 * @return The associated record, or null.
	 */
	public T getRecordForRow( Row row )
	{
		return records.get( row );
	}
	
	private void registerCurrentEdition( Cell cell, T record )
	{
		// maybe cancel current edition
		cancelCurrentEdition();
		
		// create an editor
		int column = cell.getCellIndex();
		currentEditor = columns.get( column ).column.editCell( record );
		
		if( currentEditor == null )
			return;
		
		// register current edition
		editedCell = cell;
		editedRecord = record;
		
		// register editor as current and begin talk with it
		currentEditor.setHost( editorHost );
		
		Widget editorWidget = currentEditor.getWidget();
		Style s = editorWidget.getElement().getStyle();
		s.setWidth( 100, Unit.PCT );
		s.setProperty( "border", "none" );
		s.setPadding( 0, Unit.PX );
		s.setHeight( 100, Unit.PCT );
		
		cell.setWidget( editorWidget );
	}
	
	private final IEditorHost editorHost = new IEditorHost()
	{
		@Override
		public Size getPreferredSize()
		{
			if( editedCell == null )
				return null;
			
			return editedCell.getDisplaySize();
		}
		
		@Override
		public void finishedEdition()
		{
			cancelCurrentEdition();
			table.setFocus( true );
		}
	};
	
	private void cancelCurrentEdition()
	{
		if( editedCell != null )
		{
			printRecordOnRow( editedRecord, editedCell.getParentRow() );
			editedCell = null;
		}
		
		if( currentEditor != null )
		{
			currentEditor.setHost( null );
			currentEditor = null;
		}
		
		editedRecord = null;
	}
	
	public void redraw()
	{
		for( T record : rows.keySet() )
			dataPlug.updated( record );
	}
	
	public void visitDepthFirstPre( Visitor visitor )
	{
		table.getRootRow().visitDepthFirstPre( visitor );
	}
	
	public void visitDeepFirst( Row initRow, Visitor visitor )
	{
		initRow.visitDepthFirstPre( visitor );
	}
	
	public interface DataPlug
	{
		void deleted( T record );
		void updated( T record );
		void clearAll();
	}
	
	DataPlug dataPlug = new DataPlug()
	{
		@Override
		public void deleted( T oldRecord )
		{
			Row row = rows.get( oldRecord );
			if( row != null )
				row.remove();
			
			forgetRow( oldRecord );
		}

		@Override
		public void updated( T record )
		{
			Row parentRow = getShouldBeParentRow( record );
			
			Row row = rows.get( record );
			if( row == null )
			{
				// create the row
				int insertPosition = getInsertPosition( record, parentRow );
				if( insertPosition < 0 )
				{
					row = parentRow.addRow();
				}
				else
				{
					row = parentRow.insertRowAt( insertPosition );
				}
				
				storeRow( record, row );
			}
			else
			{
				boolean move = false;
				
				if( parentRow != row.getParentRow() )
					move = true;
				
				if( !move && sortFunction != null )
				{
					// check position
					int currentPosition = parentRow.getChildrenRows().indexOf( row );
					
					// previous sibling, if existing should be inferior or equal
					if( currentPosition > 0 )
					{
						if( sortFunction.compare( records.get( parentRow.getChildrenRows().get( currentPosition - 1 ) ), record ) > 0 )
							move = true;
					}
					
					// next sibling, if existing should be superior or equal
					if( ! move && currentPosition < (parentRow.getChildrenRows().size() - 1) )
					{
						if( sortFunction.compare( records.get( parentRow.getChildrenRows().get( currentPosition + 1 ) ), record ) < 0 )
							move = true;
					}
				}
					
				// if not correct, move !
				if( move )
				{
					// insert the row in the correct parent
					int insertPosition = getInsertPosition( record, parentRow );
					if( insertPosition < 0 )
					{
						parentRow.acceptAsLastChild( row );
					}
					else
					{
						parentRow.acceptAsNthChild( row, insertPosition );
					}
				}
			}
			
			printRecordOnRow( record, row );
			
			for( RowCustomizer customizer : customizers )
				customizer.onAfterPrint( row, record );
		}
		
		private int getInsertPosition( T record, Row parentRow )
		{
			if( sortFunction!=null && parentRow.hasChildren() )
			{
				int i = 0;
				for( Row childRow : parentRow.getChildrenRows() )
				{
					T childRecord = records.get( childRow );
					if( childRecord == record )
						continue;
					int res = sortFunction.compare( record, childRecord );
					if( res <= 0 )
					{
						return i;
					}
					
					i++;
				}
			}
			
			// last
			return -1;
		}
		
		private Row getShouldBeParentRow( T record )
		{
			if( record == null )
				return null;
			
			if( hierarchyFunction == null )
				return table.getRootRow();
			
			T parentRecord = hierarchyFunction.exec( record );
			if( parentRecord == null )
				return table.getRootRow();
			
			Row parentRow = rows.get( parentRecord );
			if( parentRow == null )
				throw new RuntimeException( "Parent row not found ! You should insert parents first." );
			
			return parentRow;
		}

		@Override
		public void clearAll()
		{
			// remove from table
			for( Row row : rows.values() )
				row.remove();
			
			// forget all rows
			forgetAllRows();
		}
	};

	public T getSelectedRecord()
	{
		Cell selectedCell = selection.getSelectedCell();
		if( selectedCell == null )
			return null;
		return getRecordForRow( selectedCell.getParentRow() );
	}

	public List getSelectedRecords()
	{
		List cells = selection.getSelectedCells();
		List records = new ArrayList<>();
		for( Cell cell : cells )
			records.add( getRecordForRow( cell.getParentRow() ) );
		return records;
	}

	@Override
	public void fireEvent( GwtEvent event )
	{
		bus.fireEvent( event );
	}

	@Override
	public HandlerRegistration addSelectionHandler( final SelectionHandler handler )
	{
		return bus.addHandler( SelectionEvent.getType(), handler );
	}

	public int getColumnIndex( IColumn column )
	{
		for( int i=0; i




© 2015 - 2025 Weber Informatics LLC | Privacy Policy