view src/econnect/wp3_3/client/widgets/table/DynamicStiTable.java @ 59:3a7691d29566 CellTable

Sorting of the columns in the CellTable.
author Sebastian Kruse <skruse@mpiwg-berlin.mpg.de>
date Tue, 11 Dec 2012 17:14:26 +0100
parents 39b4d5d590ba
children aa1808d94d80
line wrap: on
line source

package econnect.wp3_3.client.widgets.table;

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

import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler;
import com.google.gwt.user.cellview.client.SimplePager;
import com.google.gwt.user.cellview.client.TextColumn;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FormPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.Hidden;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.view.client.CellPreviewEvent;
import com.google.gwt.view.client.CellPreviewEvent.Handler;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.Range;
import com.google.gwt.view.client.RangeChangeEvent;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.dom.client.Style.BorderStyle;
import com.google.gwt.dom.client.TableCellElement;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler; 
import com.google.gwt.event.dom.client.MouseOutHandler; 
import com.google.gwt.http.client.URL;

import econnect.wp3_3.client.core.ApplicationConstants;
import econnect.wp3_3.client.core.DataObject;
import econnect.wp3_3.client.core.DataSet;
import econnect.wp3_3.client.core.StiConstants;
import econnect.wp3_3.client.core.StiCore;
import econnect.wp3_3.client.services.ExportWriterInterface;
import econnect.wp3_3.client.services.ExportWriterInterfaceAsync;

/**
 * Implementation of a dynamic table for one dataset
*/
public class DynamicStiTable extends Grid {

	/**
     * The maximum number of rows of the table 
    */
	private int maxRows = 10;
	
	/**
     * The sti core component 
    */
	private StiCore core;

	/**
     * The index of the dataset
    */
	private int index;
	
	/**
     * The dataset of this dynamic table 
    */
	private DataSet dataSet;
	
	/**
     * ArrayList that holds all elements that can be shown 
     * (smaller than "dataSet" if showSelected)
    */	
	private ArrayList<DataObject> actualObjectSet;
	
	/**
     * Provider that hands the data to the table
    */
	private ListDataProvider<DataObject> dataProvider;
	
	/**
     * CellTable which displays the data
    */	
	private CellTable<DataObject> elementsTable;
	
	/**
     * The dataobjects, which are presented at the actual page 
    */
	//private DataObject[][] displayedObjects;

	/**
     * The number-of-results label 
    */
	private Label results;
	
	/**
     * The image for show selected elements 
    */
	private Image showSelected;
	
	/**
     * The image for show all elements 
    */
	private Image showAll;

	/**
     * This value tells the view status: 1 for showAll, -1 for showSelected, 0 otherwise 
    */
	private int viewAll = 1;
	
	/**
     * Anchor that holds the URL for the KML download
    */	
	private Anchor aDownloadKML;
	
	/**
     * RPC Interface for writing the KML file
    */	
	private ExportWriterInterfaceAsync exportWriter;

	/**
     * Constructor for initialization of the dynamic table
     * 
     * @param core object to allow interaction with all javascript components
     * @param id id of the dataset
    */
	public DynamicStiTable( StiCore stiCore, final int id ){
		
		final ApplicationConstants constants = (ApplicationConstants) GWT.create(ApplicationConstants.class);
		final StiConstants textConstants = (StiConstants) GWT.create(StiConstants.class);
		
		this.resize(2,1);
		this.addStyleName("dataTable");
		this.addStyleName("center");
			    
		this.index = id;
		this.core = stiCore;
		this.dataSet = core.getDataSets().get(index);
		this.actualObjectSet = new ArrayList<DataObject>();
		setActualObjectSet();
		
		showAll =  new Image("images/viewAll"+(core.getColorId(index))+".png");
		showAll.setTitle(textConstants.showAll());
		showSelected =  new Image("images/viewSelected"+(core.getColorId(index))+".png");
		showSelected.setTitle(textConstants.showSelected());
		showAll.addStyleName("selectedView");
		showAll.addStyleName("view");
		showSelected.addStyleName("view");
		showAll.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				if( viewAll != 1 ){
					showSelected.removeStyleName("selectedView");
					showAll.addStyleName("selectedView");
					viewAll = 1;
					setActualObjectSet();
				}
			}
		});
		showSelected.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				if( viewAll != -1 ){
					viewAll = -1;
					if( setActualObjectSet() ){
						showAll.removeStyleName("selectedView");
						showSelected.addStyleName("selectedView");
					}
					else {
						viewAll = 1;
					}
				}
			}
		});
		
		Image storeSelected = new Image("images/storeSelected"+(core.getColorId(index))+".png");
		storeSelected.setTitle(textConstants.storeSelected());
		storeSelected.addStyleName("view");		
		storeSelected.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				core.storeSelected(id);
			}
		});
		
		Grid showOptions = new Grid(1,3);
		showOptions.setWidget(0,0,showAll);
		showOptions.setWidget(0,1,showSelected);
		showOptions.setWidget(0,2,storeSelected);
		
		final TextBox textualSearch = new TextBox();
		final Button search = new Button("go");
		final Image cancelImage = new Image(constants.cancelImage());
		final Image refineImage = new Image(constants.refineImage());
		final Grid buttons = new Grid(1,2);
		buttons.setWidget(0, 0, refineImage);
		buttons.setWidget(0, 1, cancelImage);
		final Grid refine = new Grid(1,3);
		refine.setWidget(0,0,textualSearch);
		refine.setWidget(0,1,search);
		search.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				if( !textualSearch.getText().equals("") ){
					refineByText(textualSearch.getText());
					textualSearch.setEnabled(false);
					refine.setWidget(0, 1, buttons);
				}
			}
		});
		refineImage.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {				
				core.refine();
			}
		});
		cancelImage.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				textualSearch.setEnabled(true);
				core.reset();
				
				//TODO: das gibt es nun mehrfach im Code, sollte also in eine Funktion
				//überfuhrt werden
				showSelected.removeStyleName("selectedView");
				showAll.addStyleName("selectedView");
				viewAll = 1;
				setActualObjectSet();
				
				refine.setWidget(0, 1, search);
			}
		});
		
		this.results = new Label();	
		this.results.setStyleName("resultsLabel");
		
		Label page = new Label(textConstants.page()+":");
		page.setStyleName("pageLabel");
		
		//This anchor will hold the URL of the export-file
		//for download from the ExportServlet
		aDownloadKML = new Anchor("download KML");
		aDownloadKML.setVisible(false);
		aDownloadKML.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				aDownloadKML.setVisible(false);
			}
		});
		
		Image export = new Image(constants.exportImage());
		export.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				//create KML file
				createKMLFile();
			}
		});
		export.addStyleName("export");
		export.setTitle(textConstants.exportDataSet());

		Image delete = new Image(constants.deleteImage());
		delete.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				core.deleteDataSet(index);
			}
		});
		delete.addStyleName("delete");
		delete.setTitle(textConstants.deleteDataSet());
		
		Grid func = new Grid(1,3);

		func.setWidget(0, 0, aDownloadKML);
		func.setWidget(0, 1, export);
		func.setWidget(0, 2, delete);
		func.addStyleName("center");
		
		this.elementsTable = new CellTable<DataObject>();

		dataProvider = new ListDataProvider<DataObject>();
	    dataProvider.addDataDisplay(this.elementsTable);
		
	    TextColumn<DataObject> nameColumn = new TextColumn<DataObject>() {
	        @Override
	        public String getValue(DataObject object) {
	          return object.getName();
	        }
	    };
	    nameColumn.setSortable(true);
	    this.elementsTable.addColumn(nameColumn, "Name");

	    TextColumn<DataObject> placeColumn = new TextColumn<DataObject>() {
	        @Override
	        public String getValue(DataObject object) {
	          return object.getPlace();
	        }
	    };
	    placeColumn.setSortable(true);
	    this.elementsTable.addColumn(placeColumn, "Place");
	    
	    TextColumn<DataObject> descriptionColumn = new TextColumn<DataObject>() {
	        @Override
	        public String getValue(DataObject object) {
	          return object.getDescription();
	        }
	    };
	    descriptionColumn.setSortable(true);
	    this.elementsTable.addColumn(descriptionColumn, "Description");

	    SimplePager pager = new SimplePager();
	    pager.setDisplay(this.elementsTable);

	    dataProvider.setList(this.actualObjectSet);
	    
	    //The actual (at this time lexicographical) sorting routine.
	    //TODO: remove redundant code, make this more abstract
	    ListHandler<DataObject> columnSortHandler = new ListHandler<econnect.wp3_3.client.core.DataObject>(dataProvider.getList());
	    columnSortHandler.setComparator(nameColumn,
	    		new Comparator<econnect.wp3_3.client.core.DataObject>() {
	    			public int compare(econnect.wp3_3.client.core.DataObject o1, econnect.wp3_3.client.core.DataObject o2) {
	    				if (o1 == o2)
	    					return 0;

	    				if (o1 != null)
	    					return (o2 != null) ? o1.getName().compareTo(o2.getName()) : 1;
	                
	    				return -1;
	    			}
	    		});
	    columnSortHandler.setComparator(placeColumn,
	    		new Comparator<econnect.wp3_3.client.core.DataObject>() {
	    			public int compare(econnect.wp3_3.client.core.DataObject o1, econnect.wp3_3.client.core.DataObject o2) {
	    				if (o1 == o2)
	    					return 0;

	    				if (o1 != null)
	    					return (o2 != null) ? o1.getPlace().compareTo(o2.getPlace()) : 1;
	                
	    				return -1;
	    			}
	    		});
	    columnSortHandler.setComparator(descriptionColumn,
	    		new Comparator<econnect.wp3_3.client.core.DataObject>() {
	    			public int compare(econnect.wp3_3.client.core.DataObject o1, econnect.wp3_3.client.core.DataObject o2) {
	    				if (o1 == o2)
	    					return 0;

	    				if (o1 != null)
	    					return (o2 != null) ? o1.getDescription().compareTo(o2.getDescription()) : 1;
	                
	    				return -1;
	    			}
	    		});
	    this.elementsTable.addColumnSortHandler(columnSortHandler);
	    
	    //Redraw row colors on page change
	    this.elementsTable.addRangeChangeHandler(new RangeChangeEvent.Handler(){
	    	public void onRangeChange(RangeChangeEvent event){
	    		updateView(false);
	    	}
	    });
	    
	    this.elementsTable.setColumnWidth(nameColumn, "33%");
	    this.elementsTable.setColumnWidth(placeColumn, "33%");
	    this.elementsTable.setColumnWidth(descriptionColumn, "33%");
	    
	    //This handler adds the hover-functionality to the table,
	    //basically highlighting of rows (and in the map/timeplot)
	    this.elementsTable.addCellPreviewHandler(new Handler<DataObject>()
	    	    {
	    	        //@Override
	    	        public void onCellPreview(
	    	            CellPreviewEvent<DataObject> event)
	    	        {
	    	        	boolean wasChanged = false;
	    	        	
	    	            if ("mouseover".equals(event.getNativeEvent().getType())) {
	    	            	DataObject hoveredObject = event.getValue();
	    	            	
	    	            	if (!hoveredObject.getHover()){
	    	            		hoveredObject.setHover(true);

	    	            		wasChanged = true;
	    	            	}
	    	            }
	    	            else if ("mouseout".equals(event.getNativeEvent().getType())) {
	    	            	DataObject hoveredObject = event.getValue();
	    	            	
	    	            	if (hoveredObject.getHover()){
	    	            		hoveredObject.setHover(false);
		    	            	
	    	            		wasChanged = true;
	    	            	}
	    	            }
	    	            else if ("click".equals(event.getNativeEvent().getType())) {
	    	            	DataObject clickedObject = event.getValue();
	    	            	
	    	            	if (clickedObject.getPercentage() > 0){
	    	            		clickedObject.setPercentage(0);

	    	            	} else {
	    	            		clickedObject.setPercentage(1);
	    	            	}
	    	            	
	    	            	wasChanged = true;
	    	            }
	    	            
	    	            if (wasChanged) {
	    	            	updateView(true);
	    	            	core.updateTimeAndMap();
	    	            }
	    	        }
	    	    });
	    
	    this.setWidget(1, 0, this.elementsTable);
	    
		Grid tableControls = new Grid(1,5);
		tableControls.setWidget(0,0,showOptions);
		tableControls.setWidget(0,1,refine);
		tableControls.setWidget(0,2,this.results);
		tableControls.setWidget(0,3,pager);
		tableControls.setWidget(0,4,func);
		tableControls.setStyleName("tableControls");
		
		this.setWidget(0, 0, tableControls);

		tableControls.getCellFormatter().setHorizontalAlignment(0, 0, HasHorizontalAlignment.ALIGN_CENTER);

		this.getCellFormatter().setHorizontalAlignment(0, 0, HasHorizontalAlignment.ALIGN_CENTER);
		this.getCellFormatter().setHorizontalAlignment(1, 0, HasHorizontalAlignment.ALIGN_CENTER);
		this.getCellFormatter().setWidth(1, 0, "100%");		
	}
	
    /**
     * Sets the actual object set; depends on the showAll and showSelected selection
     * 
     * @return boolean value, if there are selected objects
    */
	private boolean setActualObjectSet(){
		
		boolean dataSetWasEmpty = false;
		
		if( viewAll == -1 ){

			this.actualObjectSet.clear();
			
			for( int i=0; i<this.dataSet.getObjects().length(); i++){
				DataObject object = this.dataSet.getObjects().get(i);
				if( object.isSelected() ){
					this.actualObjectSet.add(object);
				}
			}
			if( this.actualObjectSet.size() == 0 ){
				//TODO: Wenn die derzeitige Auswahl _keine_ Elemente
				//enthält, werden _alle_ angezeigt. Ist das sinnvoll?
				dataSetWasEmpty = true;
				this.viewAll = 1;
			}
		}
		if( this.viewAll == 1 ){
			this.actualObjectSet.clear();
			for( int i=0; i<this.dataSet.getObjects().length(); i++){
				this.actualObjectSet.add(this.dataSet.getObjects().get(i));	
			}
		}
		
		//if the dataProvider already exists, refresh the
		//displayed data
		if (this.dataProvider != null) {
			
			//refresh data
			this.dataProvider.refresh();
			this.elementsTable.setRowCount(this.actualObjectSet.size());

			this.updateView(false);
		}
		
		if (dataSetWasEmpty)
			return false;
		else
			return true;
	}
	
    /**
     * Creates a (preliminary) KML file from the data in the current data set  
    */
	private void createKMLFile(){
		String kmlContent = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><kml xmlns=\"http://www.opengis.net/kml/2.2\"><Document>";
		
		for( int i=0; i<this.dataSet.getObjects().length(); i++){
			DataObject currentDataObject = this.dataSet.getObjects().get(i);
			
			String name = currentDataObject.getName();
			String description = currentDataObject.getDescription();
			String place = currentDataObject.getPlace();
			String lat = currentDataObject.getLat();
			String lon = currentDataObject.getLon();
			String timeStamp = currentDataObject.getTimeStamp();
			String timeSpanStart = currentDataObject.getTimeSpanStart();
			String timeSpanEnd = currentDataObject.getTimeSpanEnd();
			
			String kmlEntry = "<Placemark>";

			kmlEntry += "<name><![CDATA[" + name + "]]></name>";
			kmlEntry += "<address><![CDATA[" + place + "]]></address>";
			kmlEntry += "<description><![CDATA[" + description + "]]></description>";
			kmlEntry += "<Point><coordinates>" + lon + "," + lat + "</coordinates></Point>";
			
			if ( (timeStamp != null) && (timeStamp != "undefined") ) {
				kmlEntry += "<TimeStamp><when>" + timeStamp + "</when></TimeStamp>";
			} else {
				kmlEntry += "<TimeSpan><begin>" + timeSpanStart + "</begin><end>" + timeSpanEnd + "</end></TimeSpan>";				
			}

			kmlEntry += "</Placemark>";
			
			kmlContent += kmlEntry;
		}
		
		kmlContent += "</Document></kml>";

        exportWriter = GWT.create(ExportWriterInterface.class);
        
		exportWriter.writeKMLFile(kmlContent, 
									new AsyncCallback<String>() {
            							public void onFailure(Throwable caught) {
            								//TODO: error message in layover?
            								//there needs to be an error-window for KML parser failures anyway
            								//this one should be used
            								int i = 1;
            							}
            							public void onSuccess(String exportFileName) {

            				                String url = GWT.getModuleBaseURL();
            				                url = url + "ExportServlet?ExportedFilename=" + URL.encode(exportFileName);
            				                
            				                aDownloadKML.setHref(url);
            				                aDownloadKML.setVisible(true);
            							}
									}
            					);
	}
	
    /**
     * Refines the currently selected dataset (table) by the search term entered.
     * 
    */
	private void refineByText(String searchString){
		
		String lowerCaseSearchString = searchString.toLowerCase();
		
		for( int i=0; i<this.dataSet.getObjects().length(); i++){
			DataObject currentDataObject = this.dataSet.getObjects().get(i);
			
			String name = currentDataObject.getName();
			String description = currentDataObject.getDescription();
			String place = currentDataObject.getPlace();
			
			if (	name.toLowerCase().contains(lowerCaseSearchString) || 
					description.toLowerCase().contains(lowerCaseSearchString) ||
					place.toLowerCase().contains(lowerCaseSearchString) )
				currentDataObject.setPercentage(1);
			else 
				currentDataObject.setPercentage(0);
		}
		
		updateView(false);
		core.updateTimeAndMap();
	}
	
    /**
     * Updates all cells of the actual page
     * 
     * @param hover if there was a hover selection which caused to trigger this function
    */
	public void updateView(boolean hover){
		if( !hover && this.viewAll == -1 ){
			showSelected.removeStyleName("selectedView");
			this.viewAll = 0;		
		}
		
		//redraw colors of elements (selected/unselected)
	    int start = this.elementsTable.getVisibleRange().getStart();
	    for (int i=0; i<this.elementsTable.getPageSize(); i++) {
	    	
	    	if ((start+i) >= dataProvider.getList().size())
	    		break;
	    	
	    	DataObject object = dataProvider.getList().get(start+i);
	    	
			boolean selected = object.isSelected();
			if (hover)
				selected = object.getHover();

			double percentage = (new Double(object.getPercentage()).doubleValue());
			String borderColor = StiCore.getBorderColor(this.index,selected);
			String cellColor = StiCore.getCellColor(this.index,percentage);

			//TODO: das RowElement ist ein TR. Das hat kein Border, weswegen es
			//auch keine Farbe bekommt. Erst einmal allen sub-Elementen die
			//border-Color geben. Das sollte verbessert werden, da DOM-Iterieren
			//langsam ist.
			
			NodeList<TableCellElement> cells = this.elementsTable.getRowElement(i).getCells();
			
			for (int cellIndex=0; cellIndex < cells.getLength(); cellIndex++){
				cells.getItem(cellIndex).getStyle().setBorderColor(borderColor);
			}
			
			this.elementsTable.getRowElement(i).getStyle().setBorderColor(borderColor);
			this.elementsTable.getRowElement(i).getStyle().setBackgroundColor(cellColor);
	    }
	}

	public String getTermIdentifier() {
		return this.dataSet.getTermIdentifier();
	}
	
}