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

org.got5.tapestry5.jquery.components.AbstractTable Maven / Gradle / Ivy

The newest version!
package org.got5.tapestry5.jquery.components;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.apache.tapestry5.Binding;
import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.Block;
import org.apache.tapestry5.ClientElement;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.PropertyConduit;
import org.apache.tapestry5.PropertyOverrides;
import org.apache.tapestry5.Translator;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.annotations.Persist;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.annotations.SupportsInformalParameters;
import org.apache.tapestry5.beaneditor.BeanModel;
import org.apache.tapestry5.beaneditor.PropertyModel;
import org.apache.tapestry5.corelib.data.GridPagerPosition;
import org.apache.tapestry5.grid.ColumnSort;
import org.apache.tapestry5.grid.GridConstants;
import org.apache.tapestry5.grid.GridDataSource;
import org.apache.tapestry5.grid.GridSortModel;
import org.apache.tapestry5.grid.SortConstraint;
import org.apache.tapestry5.internal.TapestryInternalUtils;
import org.apache.tapestry5.internal.beaneditor.BeanModelUtils;
import org.apache.tapestry5.internal.bindings.AbstractBinding;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
import org.apache.tapestry5.ioc.internal.util.InternalUtils;
import org.apache.tapestry5.ioc.services.TypeCoercer;
import org.apache.tapestry5.services.BeanModelSource;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.TranslatorSource;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;

/**
 * @tapestrydoc
 */
@SupportsInformalParameters
public class AbstractTable implements ClientElement {

	/**
	 * The source of data for the Grid to display. This will usually be a List
	 * or array but can also be an explicit {@link GridDataSource}. For Lists
	 * and object arrays, a GridDataSource is created automatically as a wrapper
	 * around the underlying List.
	 */
	@Parameter(required = true, autoconnect = true)
	private GridDataSource source;

	/**
	 * Defines where the pager (used to navigate within the "pages" of results)
	 * should be displayed: "top", "bottom", "both" or "none".
	 */
	@Parameter(value = "top", defaultPrefix = BindingConstants.LITERAL)
	private GridPagerPosition pagerPosition;

	/**
	 * The model used to identify the properties to be presented and the order
	 * of presentation. The model may be omitted, in which case a default model
	 * is generated from the first object in the data source (this implies that
	 * the objects provided by the source are uniform). The model may be
	 * explicitly specified to override the default behavior, say to reorder or
	 * rename columns or add additional columns. The add, include, exclude and
	 * reorder parameters are only applied to a default model, not an
	 * explicitly provided one.
	 */
	@Parameter
	private BeanModel model;

	/**
	 * The number of rows of data displayed on each page. If there are more rows
	 * than will fit, the Grid will divide up the rows into "pages" and
	 * (normally) provide a pager to allow the user to navigate within the
	 * overall result set.
	 */
	@Parameter("25")
	private int rowsPerPage;

	/**
	 * The model used to handle sorting of the Grid. This is generally not
	 * specified, and the built-in model supports only single column sorting.
	 * The sort constraints (the column that is sorted, and ascending vs.
	 * descending) is stored as persistent fields of the Grid component.
	 */
	@Parameter
	private GridSortModel sortModel;

	/**
	 * A comma-seperated list of property names to be added to the
	 * {@link org.apache.tapestry5.beaneditor.BeanModel}. Cells for added
	 * columns will be blank unless a cell override is provided. This parameter
	 * is only used when a default model is created automatically.
	 */
	@Parameter(defaultPrefix = BindingConstants.LITERAL)
	private String add;
	
	/**
	 * Defines where block and label overrides are obtained from. By default,
	 * the Grid component provides block overrides (from its block parameters).
	 */
	@Parameter(value = "this", allowNull = false)
	private PropertyOverrides overrides;
	/**
	 * A comma-separated list of property names to be retained from the
	 * {@link org.apache.tapestry5.beaneditor.BeanModel}. Only these properties
	 * will be retained, and the properties will also be reordered. The names
	 * are case-insensitive. This parameter is only used when a default model is
	 * created automatically.
	 */
	@SuppressWarnings("unused")
	@Parameter(defaultPrefix = BindingConstants.LITERAL)
	private String include;

	/**
	 * A comma-separated list of property names to be removed from the
	 * {@link org.apache.tapestry5.beaneditor.BeanModel} . The names are
	 * case-insensitive. This parameter is only used when a default model is
	 * created automatically.
	 */
	@Parameter(defaultPrefix = BindingConstants.LITERAL)
	private String exclude;

	/**
	 * A comma-separated list of property names indicating the order in which
	 * the properties should be presented. The names are case insensitive. Any
	 * properties not indicated in the list will be appended to the end of the
	 * display order. This parameter is only used when a default model is
	 * created automatically.
	 */
	@Parameter(defaultPrefix = BindingConstants.LITERAL)
	private String reorder;

	/**
	 * The current value, set before the component renders its body.
	 */
	@SuppressWarnings("unused")
	@Parameter
	private Object value;
	
	/**
	 * If true, then the Grid will be wrapped in an element that acts like a
	 * {@link org.apache.tapestry5.corelib.components.Zone}; all the paging and
	 * sorting links will refresh the zone, repainting the entire grid within
	 * it, but leaving the rest of the page (outside the zone) unchanged.
	 */
	@Parameter
	private boolean inPlace;
	
	/**
	 * The model parameter after modification due to the add, include, exclude
	 * and reorder parameters.
	 */
	private BeanModel dataModel;

	@Inject
	private JavaScriptSupport javaScriptSupport;

	@Inject
	private ComponentResources resources;


	@Inject
	private BeanModelSource modelSource;
	
	@Inject 
	private TranslatorSource translatorSource;
	@Persist
	private Boolean sortAscending;

	@Persist
	private String sortColumnId;
	
	private String clientId;
	
	
	@Property
	private String cellModel;
	
	@Inject
	private TypeCoercer typeCoercer;

	@Inject
	private Request request;
	
	public String getClientId() {

		if (clientId == null) {

			clientId = javaScriptSupport.allocateClientId(resources);
		}

		return clientId;
	}

	public int getRowsPerPage() {
		return rowsPerPage;
	}

	public GridSortModel getSortModel() {
		return sortModel;
	}

	public boolean getSortAscending() {
		return sortAscending != null && sortAscending.booleanValue();
	}

	public void setSortAscending(boolean sortAscending) {
		this.sortAscending = sortAscending;
	}

	public BeanModel getModel() {
		return model;
	}

	public BeanModel getDataModel() {
		if (dataModel == null) {
			dataModel = getModel();

			BeanModelUtils.modify(dataModel, add, include, exclude, reorder);
		}

		return dataModel;
	}

	public GridDataSource getSource() {
		return source;
	}
	
	/**
	 * Default implementation that only allows a single column to be the sort
	 * column, and stores the sort information as persistent fields of the Grid
	 * component.
	 */
	class DefaultGridSortModel implements GridSortModel {
		public ColumnSort getColumnSort(String columnId) {
			if (!TapestryInternalUtils.isEqual(columnId, sortColumnId))
				return ColumnSort.UNSORTED;

			return getColumnSort();
		}

		private ColumnSort getColumnSort() {
			return getSortAscending() ? ColumnSort.ASCENDING
					: ColumnSort.DESCENDING;
		}

		public void updateSort(String columnId) {
			assert InternalUtils.isNonBlank(columnId);
			if (columnId.equals(sortColumnId)) {
				setSortAscending(!getSortAscending());
				return;
			}

			sortColumnId = columnId;
			setSortAscending(true);
		}

		public List getSortConstraints() {
			if (sortColumnId == null)
				return Collections.emptyList();

			PropertyModel sortModel = getDataModel().getById(sortColumnId);

			SortConstraint constraint = new SortConstraint(sortModel,
					getColumnSort());

			return Collections.singletonList(constraint);
		}

		public void clear() {
			sortColumnId = null;
		}
	}

	GridSortModel defaultSortModel() {
		return new DefaultGridSortModel();
	}
	
	public PropertyOverrides getOverrides(){ 
		return overrides; 
	}
	/**
	 * Returns a {@link org.apache.tapestry5.Binding} instance that attempts to
	 * identify the model from the source parameter (via
	 * {@link org.apache.tapestry5.grid.GridDataSource#getRowType()}. Subclasses
	 * may override to provide a different mechanism. The returning binding is
	 * variant (not invariant).
	 * 
	 * @see BeanModelSource#createDisplayModel(Class,
	 *      org.apache.tapestry5.ioc.Messages)
	 */
	protected Binding defaultModel() {
		return new AbstractBinding() {
			public Object get() {
				// Get the default row type from the data source

				GridDataSource gridDataSource = (GridDataSource) getSource();

				Class rowType = gridDataSource.getRowType();

				if (rowType == null)
					throw new RuntimeException(
							String.format(
									"Unable to determine the bean type for rows from %s. You should bind the model parameter explicitly.",
									gridDataSource));

				// Properties do not have to be read/write

				return modelSource.createDisplayModel(rowType,
						overrides.getOverrideMessages());
			}

			/**
			 * Returns false. This may be overkill, but it basically exists
			 * because the model is inherently mutable and therefore may contain
			 * client-specific state and needs to be discarded at the end of the
			 * request. If the model were immutable, then we could leave
			 * invariant as true.
			 */
			@Override
			public boolean isInvariant() {
				return false;
			}
		};
	}
	
	/**
	 * A version of GridDataSource that caches the availableRows property. This
	 * addresses TAPESTRY-2245.
	 */
	static class CachingDataSource implements GridDataSource {
		private final GridDataSource delegate;

		private boolean availableRowsCached;

		private int availableRows;

		CachingDataSource(GridDataSource delegate) {
			this.delegate = delegate;
		}

		public int getAvailableRows() {
			if (!availableRowsCached) {
				availableRows = delegate.getAvailableRows();
				availableRowsCached = true;
			}

			return availableRows;
		}

		public void prepare(int startIndex, int endIndex,
				List sortConstraints) {
			delegate.prepare(startIndex, endIndex, sortConstraints);
		}

		public Object getRowValue(int index) {
			return delegate.getRowValue(index);
		}

		public Class getRowType() {
			return delegate.getRowType();
		}
		
		
	}
	
	@Parameter(required = true)
    @Property(write = false)
    private Object row;
	
	@Parameter
    private int rowIndex;
	
	@Parameter(cache = false)
	private String rowClass;
	
	@Property
	private Integer index;

	/**
	 * In order get the value of a specific cell
	 */
	public String getRowClass()
    {
        List classes = CollectionFactory.newList();

        // Not a cached parameter, so careful to only access it once.

       String rc = rowClass;

       if (rc != null) classes.add(rc);

       return TapestryInternalUtils.toClassAttributeValue(classes);
    }
	
	public Object getCellValue() {
		
		rowIndex = index;
		
		Object obj = getSource().getRowValue(index);

		if (obj == null) { //rows can be null, as stated in getRowValue docs
		    return "";
		}
		
		PropertyConduit conduit = getDataModel().get(cellModel).getConduit();

		Class type = conduit.getPropertyType();

		Object val = conduit.get(obj);

		if (val == null) { //cells should be able to have null values
		    return "";
		}
		
		if (!String.class.equals(getDataModel().get(cellModel).getClass())
                && !Number.class.isAssignableFrom(getDataModel().get(cellModel).getClass()))
        {
			Translator translator = translatorSource.findByType(getDataModel().get(cellModel).getPropertyType());
            if (translator != null)
            {
            	val = translator.toClient(val);
            }
            else
            {
            	val = val != null ? val.toString() : "";
            }
        }
            
		return val;
	}

	/**
	 * source of the seconf loop component, in order to loop on each cells
	 */
	public List getPropertyNames() {
		return (List) getDataModel().getPropertyNames();
	}

	/**
	 * Iterator for the look component in order to loop to each rows
	 */
	public Iterable getLoopSource() {
		return new Iterable() {

			public Iterator iterator() {

				return new Iterator() {

					Integer i = new Integer(0);

					public boolean hasNext() {
						
						return i < getSource().getAvailableRows();
					}

					public Integer next() {
						row=getSource().getRowValue(i);
						return i++;
					}

					public void remove() {
						i = 0;
					}
				};

			}
		};
	}
	
	@Inject
	private Block cell;
	
	public Block getCellBlock(){
		Block override = overrides.getOverrideBlock(getDataModel().get(cellModel).getPropertyName()+"Cell");
		if(override != null) return override;
		return cell;
	}

}