cancel
Showing results for 
Search instead for 
Did you mean: 

Table Sorting: How is firstVisibleRow being affected?

Former Member
0 Kudos

Dears,

I'm confused at table sorting using Tablesorter.java.

When sorting, how does Tablesorter affect the firstVisibleRow of the table?

In the "[Working with tables|https://www.sdn.sap.com/irj/sdn/go/portal/prtroot/docs/library/uuid/90ac0015-d1c5-2a10-d788-aed89990811d]" sample SDN provided, when press "sorting asending" or "sort descending", the table's first visible row changes automatically.

I didn't understand why firstVisibleRow changed to be so. And I don't know how the firstVisibleRow will change to be.

I checked the code of Tablesorter, and I also tried to debug into it, but didn't find any track.

Any word is appreciated. Thanks in advance.

Edited by: gangtee gangtee on May 2, 2008 4:59 PM

Accepted Solutions (1)

Accepted Solutions (1)

former_member201361
Active Contributor
0 Kudos

hi,

Whether table sorting takes place for all the columns or not ?

please throw some light on ur requirements .

when sorting is done based on that all the table row would be arranged in the ascending order or descending order with respect to the selected Column.

Thanks and regards

Fistae

Former Member
0 Kudos

Hi Fistae,

Thanks for your reply.

My requirement is just to sort a table according to one String column of the table.

If it happened that you had seen the "Working with table" tutorials & samples,

my requirement is no more than that.

Actually I'm studying the SDN sample, what I described is the sample's result, I just want to know the technical details.

How is the table UI element's firstVisibleRow property changed by Tablesorter.java?

Edited by: gangtee gangtee on May 2, 2008 5:20 PM

Former Member
0 Kudos

I understand now.

After sortElements, the firstVisibleRow of the table may change depending on the leadSelection row of the table.

No matter how many pages the table contains,

the currently diaplayed page after sorting is always the page containing the leadSelection row.

If there is enough rows below the leadSelection row to fullfill the current page,

the leadSelection row will be the firstVisibleRow.

If not,

the leadSelection row will be put lower some rows to fullfill the current page.

Thanks everyone.

Answers (2)

Answers (2)

Former Member
0 Kudos

Got the explanation.

Former Member
0 Kudos

Attached is Tablesorter.java

package com.sap.tc.webdynpro.tests.utils;

import java.text.Collator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;

import com.sap.tc.webdynpro.clientserver.uielib.standard.api.IWDAbstractDropDownByIndex;
import com.sap.tc.webdynpro.clientserver.uielib.standard.api.IWDAbstractDropDownByKey;
import com.sap.tc.webdynpro.clientserver.uielib.standard.api.IWDAbstractInputField;
import com.sap.tc.webdynpro.clientserver.uielib.standard.api.IWDAbstractTableColumn;
import com.sap.tc.webdynpro.clientserver.uielib.standard.api.IWDCaption;
import com.sap.tc.webdynpro.clientserver.uielib.standard.api.IWDCheckBox;
import com.sap.tc.webdynpro.clientserver.uielib.standard.api.IWDLink;
import com.sap.tc.webdynpro.clientserver.uielib.standard.api.IWDProgressIndicator;
import com.sap.tc.webdynpro.clientserver.uielib.standard.api.IWDRadioButton;
import com.sap.tc.webdynpro.clientserver.uielib.standard.api.IWDTable;
import com.sap.tc.webdynpro.clientserver.uielib.standard.api.IWDTableCellEditor;
import com.sap.tc.webdynpro.clientserver.uielib.standard.api.IWDTableColumn;
import com.sap.tc.webdynpro.clientserver.uielib.standard.api.IWDTableColumnGroup;
import com.sap.tc.webdynpro.clientserver.uielib.standard.api.IWDTextEdit;
import com.sap.tc.webdynpro.clientserver.uielib.standard.api.IWDTextView;
import com.sap.tc.webdynpro.clientserver.uielib.standard.api.WDTableColumnSortDirection;
import com.sap.tc.webdynpro.progmodel.api.IWDAction;
import com.sap.tc.webdynpro.progmodel.api.IWDCustomEvent;
import com.sap.tc.webdynpro.progmodel.api.IWDNode;
import com.sap.tc.webdynpro.progmodel.api.IWDNodeElement;
import com.sap.tc.webdynpro.progmodel.api.IWDViewElement;
import com.sap.tc.webdynpro.services.sal.localization.api.WDResourceHandler;

/**
 * Helper class that makes a Web Dynpro table UI element sortable (column-wise).
 */
public final class TableSorter {
	/**
	 * @param table
	 * @param sortAction
	 * @param comparators
	 */
	
	public TableSorter(IWDTable table, IWDAction sortAction, Map comparators) {
		init(table, sortAction, comparators, null);
	}
	
	public TableSorter(IWDTable table, IWDAction sortAction, Map comparators, String[] sortableColumns) {
		init(table, sortAction, comparators, sortableColumns);
	}
	
	/**
	 * Initialisation stuff
	 */
	private void init(IWDTable table, IWDAction sortAction, Map comparators, String[] sortableColumns){
		this.table = table;
		if(sortableColumns == null){
			sortableCols = null;
		}else{
			sortableCols = new HashMap();
			for (int i = 0; i < sortableColumns.length; i++) {
				sortableCols.put(sortableColumns<i>, sortableColumns<i>);
			}
		}

		// sanity checks
		if (sortAction == null)
			throw new IllegalArgumentException("Sort action must be given");
		if (table == null)
			throw new IllegalArgumentException("Table must be given");
		if (table.bindingOfDataSource() == null)
			throw new IllegalArgumentException(
				"Data source of table with id '" + table.getId() + "' must be bound");

		// make the columns sortable
		String dataSourcePrefix = table.bindingOfDataSource() + ".";
		setComparatorsForColumns(dataSourcePrefix, table.iterateGroupedColumns(), comparators);
		
		//set up the table properties
		table.setOnSort(sortAction);
		table.mappingOfOnSort().addSourceMapping(IWDTable.IWDOnSort.COL, "selectedColumn");
		table.mappingOfOnSort().addSourceMapping(IWDTable.IWDOnSort.DIRECTION, "sortDirection");	
	}
	
	/**
	 * Try to make the given columns sortable (recusivly, if necessary)
	 */
	private void setComparatorsForColumns(String dataSourcePrefix, Iterator columnIterator, Map comparators){
		int index = 0;
		for (Iterator it = columnIterator; it.hasNext(); ++index) { // for every column: try to make it bindable
			IWDAbstractTableColumn abstractColumn = (IWDAbstractTableColumn) it.next();
			if(abstractColumn instanceof IWDTableColumn){
				
				IWDTableColumn column = (IWDTableColumn)abstractColumn;
				if(sortableCols == null || sortableCols.containsKey(column.getId())){
					//try to make this column sortable
					Comparator comparator = null;
					if (comparators != null){
						comparator = (Comparator)comparators.get(column.getId());
					}
					
					NodeElementByAttributeComparator elementComparator = null;	
					if (comparator instanceof NodeElementByAttributeComparator) {
						// the easy one, attribute and ordering are given
						elementComparator = (NodeElementByAttributeComparator)comparator;
					} else { // attribute must be determined
						String bindingOfPrimaryProperty = bindingOfPrimaryProperty(column.getTableCellEditor());
						if (bindingOfPrimaryProperty == null || !bindingOfPrimaryProperty.startsWith(dataSourcePrefix)){
							//no attribute found or outside of data source
							column.setSortState(WDTableColumnSortDirection.NOT_SORTABLE);
							continue;
						}
						String attributeName = bindingOfPrimaryProperty.substring(dataSourcePrefix.length());
						Collection subnodes = new ArrayList();
						if (attributeName.indexOf('.') >= 0){
							//attribute not immediately below data source
							String[] tokens = tokenize (attributeName, ".");
							for(int i=0; i<tokens.length-1; i++){
								subnodes.add(tokens<i>);
							}
							attributeName = tokens[tokens.length-1];
						}
						if(subnodes.size() == 0){
							elementComparator = new NodeElementByAttributeComparator(attributeName, comparator);
						}else{
							elementComparator = new NodeElementByAttributeComparator(attributeName, comparator, subnodes);
						}
					}
		
					// set up internal data structures
					comparatorForColumn.put(column, elementComparator);
					
					//set sort state
					column.setSortState(WDTableColumnSortDirection.NONE);
				}else{
					//column should not be sortable
					column.setSortState(WDTableColumnSortDirection.NOT_SORTABLE);
				}
				
			}else if (abstractColumn instanceof IWDTableColumnGroup){
				//it's just a column group -> try to bind the columns of the column group
				IWDTableColumnGroup columnGroup = (IWDTableColumnGroup)abstractColumn;
				setComparatorsForColumns(dataSourcePrefix, columnGroup.iterateColumns(), comparators);
			}
			
		}		
	}
	
	/**
	 * Tokenizes the input string according to the given delimiters. The delimiters will be left out.
	 * Example: tokenize("Hello_World", "_") results ["Hello", "World"]
	 */
	private String[] tokenize (String input, String delim){
		StringTokenizer tokenizer = new StringTokenizer(input, delim);
		String[] tokens = new String[tokenizer.countTokens()];
		int index = 0;
		while(tokenizer.hasMoreTokens()){
			tokens[index] = tokenizer.nextToken();
			index++;
		}
		return tokens;
	}

	/**
	 * This method must be called from the event handler of this table sorter's
	 * sort action. It performs the actual sort operation.
	 */
	public void sort(IWDCustomEvent wdEvent, IWDNode dataSource) {
		// find the things we need
		String columnId = wdEvent.getString("selectedColumn");
		String direction = wdEvent.getString("sortDirection");
		IWDTableColumn column = (IWDTableColumn) table.getView().getElement(columnId);
		NodeElementByAttributeComparator elementComparator = (NodeElementByAttributeComparator) comparatorForColumn.get(column);
		
		if (elementComparator == null){
			//not a sortable column
			column.setSortState(WDTableColumnSortDirection.NOT_SORTABLE);
			return; 
		}
		
		// sorting
		elementComparator.setSortDirection(WDTableColumnSortDirection.valueOf(direction));
		dataSource.sortElements(elementComparator);
	}

	/**
	 * Returns the binding of the given table cell editor's property that is
	 * considered "primary" or <code>null</code> if no such binding exists or no
	 * such property can be determined.
	 */
	private static final String bindingOfPrimaryProperty(IWDTableCellEditor editor) {
		return editor instanceof IWDViewElement ? bindingOfPrimaryProperty((IWDViewElement) editor) : null;
	}

	/**
	 * Returns the binding of the given view element's property that is
	 * considered "primary" or <code>null</code> if no such binding exists or no
	 * such property can be determined.
	 */
	private static final String bindingOfPrimaryProperty(IWDViewElement element) {
		if (element instanceof IWDAbstractDropDownByIndex)
			return ((IWDAbstractDropDownByIndex) element).bindingOfTexts();
		if (element instanceof IWDAbstractDropDownByKey)
			return ((IWDAbstractDropDownByKey) element).bindingOfSelectedKey();
		if (element instanceof IWDAbstractInputField)
			return ((IWDAbstractInputField) element).bindingOfValue();
		if (element instanceof IWDCaption)
			return ((IWDCaption) element).bindingOfText();
		if (element instanceof IWDCheckBox)
			return ((IWDCheckBox) element).bindingOfChecked();
		if (element instanceof IWDLink)
			return ((IWDLink) element).bindingOfText();
		if (element instanceof IWDProgressIndicator)
			return ((IWDProgressIndicator) element).bindingOfPercentValue();
		if (element instanceof IWDRadioButton)
			return ((IWDRadioButton) element).bindingOfSelectedKey();
		if (element instanceof IWDTextEdit)
			return ((IWDTextEdit) element).bindingOfValue();
		if (element instanceof IWDTextView)
			return ((IWDTextView) element).bindingOfText();

		return null;
	}

	/**
	 * Instance of a comparator according to the ordering imposed by the
	 * implementation of <code>Comparable</code>.
	 */
	private static final Comparator DEFAULT = new Comparator() {
		/**
		 * Compares the given objects according to the ordering imposed by the first
		 * ones <code>compareTo(Object)</code> function. Furthermore, <code>null</code>
		 * is treated to be less than any object.
		 * 
		 * @see java.lang.Comparable#compareTo(java.lang.Object)
		 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
		 */
		public int compare(Object o1, Object o2) {
			if (o1 == null && o2 == null)
				return 0;
			if (o1 == null)
				return -1;
			if (o2 == null)
				return +1;
			if (o1 instanceof Boolean && o2 instanceof Boolean)
				return o1.toString().compareTo(o2.toString()); // false < true
			if (o1 instanceof String && o2 instanceof String){
				//Use a Collator for sorting according to the given Locale
				Collator collate = Collator.getInstance(WDResourceHandler.getCurrentSessionLocale());
				return collate.compare(o1, o2);				
			}
			return ((Comparable) o1).compareTo((Comparable) o2);
		}
	};

	/**
	 * Map of table column to comparator (<code>ReversableComparator</code>)
	 * used for sorting that column (sortable columns only).
	 */
	private Map comparatorForColumn = new HashMap();

	/**
	 * The table to be sorted.
	 */
	private IWDTable table = null;
	
	/**
	 * Column-IDs of the columns, which should be sortable
	 */
	private Map sortableCols = null;

	/**
	 * Generic comparator that compares node elements by a given attribute with
	 * the help of a given comparator.
	 */
	public final class NodeElementByAttributeComparator implements Comparator {
		
		
		/**
		 * Creates a new comparator for the given attribute name that compares values
		 * of that attribute according to the natural ordering of that attribute's
		 * type (which must implement <code>java.lang.Comparable</code>).
		 */
		public NodeElementByAttributeComparator(String attributeName) {
			this(attributeName, null, false, new ArrayList());
		}

		/**
		 * Creates a new comparator for the given attribute name that compares values
		 * of that attribute with the help of the given comparator. If no comparator
		 * is given, the natural ordering of that attribute's type is used.
		 */
		public NodeElementByAttributeComparator(String attributeName, Comparator comparator) {
			this(attributeName, comparator, false, new ArrayList());
		}

		/**
		 * Creates a new comparator for the given attribute name that compares values
		 * of that attribute either as objects (i.e. "in internal format") or as text
		 * (i.e. "in external format") as indicated. The ordering is the natural
		 * ordering of that attribute's type (which must implement
		 * <code>java.lang.Comparable</code>) in case objects are compared or the
		 * natural ordering of <code>java.lang.String</code> in case texts are compared.
		 */
		public NodeElementByAttributeComparator(String attributeName, boolean compareAsText) {
			this(attributeName, null, compareAsText, new ArrayList());
		}
		
		/**
		 * Creates a new comparator for the given attribute name that compares values
		 * of that attribute according to the natural ordering of that attribute's
		 * type (which must implement <code>java.lang.Comparable</code>). In addition it is possible 
		 * to define the path to a child node with the <code>java.util.Collection</code> subnodes.
		 * (List of child node names in the correct order)
		 */
		public NodeElementByAttributeComparator(String attributeName, Collection subnodes) {
			this(attributeName, null, false, subnodes);
		}

		/**
		 * Creates a new comparator for the given attribute name that compares values
		 * of that attribute with the help of the given comparator. If no comparator
		 * is given, the natural ordering of that attribute's type is used. In addition it is possible 
		 * to define the path to a child node with the <code>java.util.Collection</code> subnodes.
		 * (List of child node names in the correct order)
		 */
		public NodeElementByAttributeComparator(String attributeName, Comparator comparator, Collection subnodes) {
			this(attributeName, comparator, false, subnodes);
		}

		/**
		 * Creates a new comparator for the given attribute name that compares values
		 * of that attribute either as objects (i.e. "in internal format") or as text
		 * (i.e. "in external format") as indicated. The ordering is the natural
		 * ordering of that attribute's type (which must implement
		 * <code>java.lang.Comparable</code>) in case objects are compared or the
		 * natural ordering of <code>java.lang.String</code> in case texts are compared. In addition it is possible 
		 * to define the path to a child node with the <code>java.util.Collection</code> subnodes.
		 * (List of child node names in the correct order)
		 */
		public NodeElementByAttributeComparator(String attributeName, boolean compareAsText, Collection subnodes) {
			this(attributeName, null, compareAsText, subnodes);
		}

		/**
		 * Internal constructor.
		 */
		private NodeElementByAttributeComparator(
			String attributeName,
			Comparator comparator,
			boolean compareAsText,
			Collection subNodes) {
			if (attributeName == null)
				throw new IllegalArgumentException("Attribute name must not be null");
			if (comparator == null)
				comparator = DEFAULT;

			this.attributeName = attributeName;
			this.comparator = comparator;
			this.compareAsText = compareAsText;
			this.sortDirection = true;
			this.subNodes = subNodes;
		}
		
		/**
		 * Sets the sort direction of this comparator to the given direction. The comparator sort in ascending order by default.
		 * @see com.sap.tc.webdynpro.clientserver.uielib.standard.api.WDTableColumnSortDirection
		 */
		public void setSortDirection(WDTableColumnSortDirection direction){
			if(direction.equals(WDTableColumnSortDirection.UP)){
				sortDirection = true;
			}else if(direction.equals(WDTableColumnSortDirection.DOWN)){
				sortDirection = false;
			}
		}

		/**
		 * Compares the given objects which must be instances of <code>IWDNodeElement</code>
		 * according to the values of the attribute given at construction time
		 * with the help of the comparator given at construction time.
		 * 
		 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
		 * @see com.sap.tc.webdynpro.progmodel.api.IWDNodeElement
		 */
		public int compare(Object o1, Object o2) {
			IWDNodeElement element1 = (IWDNodeElement) o1;
			IWDNodeElement element2 = (IWDNodeElement) o2;
			if(subNodes.size() > 0){
				element1 = getSubNodeElement(element1, 0);
				element2 = getSubNodeElement(element2, 0);
			}
			Object attributeValue1 = null;
			Object attributeValue2 = null;
			if(element1 != null){
				attributeValue1 =
					compareAsText
						? element1.getAttributeAsText(attributeName)
						: element1.getAttributeValue(attributeName);
			}
			if(element2 != null){
				attributeValue2 =
					compareAsText
						? element2.getAttributeAsText(attributeName)
						: element2.getAttributeValue(attributeName);
			}

			if(sortDirection){
				return comparator.compare(attributeValue1, attributeValue2);
			}else{
				return comparator.compare(attributeValue2, attributeValue1);
			}
		}
		
		/**
		 * Determines recursivly the child node, which have an attribute with the given name.
		 * The path to this child node must be specified in the subnodes property of this comparator.
		 * Start this method with index = 0.
		 */
		private IWDNodeElement getSubNodeElement(IWDNodeElement currentElement, int index){
			if(currentElement == null || index >= subNodes.size()){
				//end of recursion
				return currentElement;
			}else{
				return getSubNodeElement(currentElement.node().getChildNode((String)subNodes.toArray()[index], currentElement.index()).getCurrentElement(), index+1);
				//return getSubNodeElement(currentElement.node().getChildNode((String)subNodes.toArray()[index], currentElement.index()).getElementAt(0), index+1);
			}
		}

		/**
		 * Name of the attribute used for comparisons.
		 */
		private final String attributeName;

		/**
		 * Comparator used for comparing the attribute's values.
		 */
		private final Comparator comparator;

		/**
		 * Indicates whether attribute values are compared as text (as opposed to
		 * "as objects").
		 */
		private final boolean compareAsText;
		
		/**
		 * Sort direction (true = ascending order, false = descending order)
		 */
		private boolean sortDirection;
		
		/**
		 * List of child node names
		 * (Description of the path from the given context node to the specified attribute)
		 */
		private Collection subNodes;
	}

}