/* * Ext GWT - Ext for GWT * Copyright(c) 2007-2009, Ext JS, LLC. * licensing@extjs.com * * http://extjs.com/license */ package com.extjs.gxt.ui.client.widget.grid; import com.extjs.gxt.ui.client.GXT; import com.extjs.gxt.ui.client.data.ModelData; import com.extjs.gxt.ui.client.data.ModelStringProvider; import com.extjs.gxt.ui.client.event.BaseEvent; import com.extjs.gxt.ui.client.event.ComponentEvent; import com.extjs.gxt.ui.client.event.Events; import com.extjs.gxt.ui.client.event.GridEvent; import com.extjs.gxt.ui.client.store.ListStore; import com.extjs.gxt.ui.client.widget.BoxComponent; import com.extjs.gxt.ui.client.widget.grid.GridSelectionModel.Callback; import com.extjs.gxt.ui.client.widget.menu.Menu; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; /** * This class represents the primary interface of a component based grid * control. * * <dl> * <dt><b>Events:</b></dt> * * <dd><b>CellClick</b> : GridEvent(grid, rowIndex, cellIndex, event)<br> * <div>Fires after a cell is clicked.</div> * <ul> * <li>grid : this</li> * <li>rowIndex : row index</li> * <li>cellIndex : cell index</li> * <li>event : the dom event</li> * </ul> * </dd> * * <dd><b>CellDoubleClick</b> : GridEvent(grid, rowIndex, cellIndex, event)<br> * <div>Fires after a cell is double clicked.</div> * <ul> * <li>grid : this</li> * <li>rowIndex : row index</li> * <li>cellIndex : cell index</li> * <li>event : the dom event</li> * </ul> * </dd> * * <dd><b>CellMouseDown</b> : GridEvent(grid, rowIndex, cellIndex, event)<br> * <div>Fires before a cell is clicked.</div> * <ul> * <li>grid : this</li> * <li>rowIndex : row index</li> * <li>cellIndex : cell index</li> * <li>event : the dom event</li> * </ul> * </dd> * * <dd><b>RowClick</b> : GridEvent(grid, rowIndex, cellIndex, event)<br> * <div>Fires after a row is clicked.</div> * <ul> * <li>grid : this</li> * <li>rowIndex : the row index</li> * <li>cellIndex : cell index</li> * <li>index : the cell index</li> * <li>event : the dom event</li> * </ul> * </dd> * * <dd><b>RowDoubleClick</b> : GridEvent(grid, rowIndex, cellIndex, event)<br> * <div>Fires after a row is double clicked.</div> * <ul> * <li>grid : this</li> * <li>rowIndex : the row index</li> * <li>index : the cell index</li> * <li>event : the dom event</li> * </ul> * </dd> * * <dd><b>RowMouseDown</b> : GridEvent(grid, rowIndex, colIndex, event)<br> * <div>Fires before a row is clicked.</div> * <ul> * <li>grid : this</li> * <li>rowIndex : row index</li> * <li>colIndex : column index</li> * <li>event : the dom event</li> * </ul> * </dd> * * <dd><b>HeaderClick</b> : GridEvent(grid, rowIndex, colIndex, event)<br> * <div>Fires a header is clicked.</div> * <ul> * <li>grid : this</li> * <li>rowIndex : row index</li> * <li>colIndex : column index</li> * <li>event : the dom event</li> * </ul> * </dd> * * <dd><b>HeaderDoubleClick</b> : GridEvent(grid, rowIndex, colIndex, event)<br> * <div>Fires a header is double clicked.</div> * <ul> * <li>grid : this</li> * <li>rowIndex : row index</li> * <li>colIndex : column index</li> * <li>event : the dom event</li> * </ul> * </dd> * * <dd><b>HeaderMouseDown</b> : GridEvent(grid, rowIndex, colIndex, event)<br> * <div>Fires before a header is clicked.</div> * <ul> * <li>grid : this</li> * <li>rowIndex : row index</li> * <li>colIndex : column index</li> * <li>event : the dom event</li> * </ul> * </dd> * * <dd><b>ContextMenu</b> : GridEvent(grid)<br> * <div>Fires before the grid's context menu is shown. Listeners can cancel the * action by calling {@link BaseEvent#setCancelled(boolean)}.</div> * <ul> * <li>grid : this</li> * </ul> * </dd> * * <dd><b>HeaderContextMenu</b> : TableEvent(grid, colIndex, menu)<br> * <div>Fires right before the header's context menu is displayed.</div> * <ul> * <li>grid : this</li> * <li>colIndex : the column index</li> * <li>menu : the context menu</li> * </ul> * </dd> * * <dd><b>BodyScroll</b> : GridEvent(grid, srollLeft, scrollTop)<br> * <div>Fires when the body element is scrolled.</div> * <ul> * <li>grid : this</li> * <li>scrollLeft : scrollLeft</li> * <li>scrollTop : scrollTop</li> * </ul> * </dd> * * <dd><b>ColumnResize</b> : GridEvent(grid, colIndex, width)<br> * <div>Fires when the user resizes a column.</div> * <ul> * <li>grid : this</li> * <li>colIndex : the column index</li> * <li>width : the new column width</li> * </ul> * </dd> * * <dd><b>ColumnMove</b> : GridEvent(grid, colIndex, size)<br> * <div>Fires when the user moves a column.</div> * <ul> * <li>grid : this</li> * <li>oldIndex : the old column index</li> * <li>newIndex : the new column index</li> * </ul> * </dd> * * <dd><b>SortChange</b> : GridEvent(grid, sortInfo)<br> * <div>Fires when the grid's store sort changes.</div> * <ul> * <li>grid : this</li> * <li>sortInfo : the sort field and direction</li> * </ul> * </dd> * </dl> * * <dl> * <dt>Inherited Events:</dt> * <dd>BoxComponent Move</dd> * <dd>BoxComponent Resize</dd> * <dd>Component Enable</dd> * <dd>Component Disable</dd> * <dd>Component BeforeHide</dd> * <dd>Component Hide</dd> * <dd>Component BeforeShow</dd> * <dd>Component Show</dd> * <dd>Component Attach</dd> * <dd>Component Detach</dd> * <dd>Component BeforeRender</dd> * <dd>Component Render</dd> * <dd>Component BrowserEvent</dd> * <dd>Component BeforeStateRestore</dd> * <dd>Component StateRestore</dd> * <dd>Component BeforeStateSave</dd> * <dd>Component SaveState</dd> * </dl> * * @param <M> the model type */ public class Grid<M extends ModelData> extends BoxComponent { protected ColumnModel cm; protected GridSelectionModel<M> sm; protected ListStore<M> store; protected GridView view; protected boolean viewReady; protected ModelStringProvider<M> stringProvider; // config private int minColumnWidth = 25; private boolean trackMouseOver = true; private boolean stripeRows; private String autoExpandColumn; private int autoExpandMax = 500; private int autoExpandMin = 25; private boolean enableColumnResize = true; private boolean hideHeaders; private boolean loadMask; protected Grid() { } /** * Creates a new grid. * * @param store the data store * @param cm the column model */ public Grid(ListStore<M> store, ColumnModel cm) { this.store = store; this.cm = cm; this.view = new GridView(); focusable = true; baseStyle = "x-grid-panel"; setSelectionModel(new GridSelectionModel<M>()); } /** * Returns the auto expand column id. * * @return the auto expand column id */ public String getAutoExpandColumn() { return autoExpandColumn; } /** * Returns the auto expand maximum width. * * @return the max width in pixels */ public int getAutoExpandMax() { return autoExpandMax; } /** * Returns the auto expand miniumum width. * * @return the minimum width in pixels */ public int getAutoExpandMin() { return autoExpandMin; } /** * Returns the column model. * * @return the colum model */ public ColumnModel getColumnModel() { return cm; } /** * Returns the minimum column width. * * @return the min width in pixels */ public int getMinColumnWidth() { return minColumnWidth; } /** * Returns the grid's selection model. * * @return the selection model */ public GridSelectionModel<M> getSelectionModel() { return sm; } /** * Returns the grid's store. * * @return the store */ public ListStore<M> getStore() { return store; } /** * Returns the grid's view. * * @return the grid view */ public GridView getView() { return view; } /** * Returns true if column resizing is enabled. * * @return true if resizing is enabled */ public boolean isEnableColumnResize() { return enableColumnResize; } /** * Returns true if the header is hidden. * * @return true for hidden */ public boolean isHideHeaders() { return hideHeaders; } /** * Returns true if the load mask in enabled. * * @return the load mask state */ public boolean isLoadMask() { return loadMask; } /** * Returns true if row striping is enabled. * * @return the strip row state */ public boolean isStripeRows() { return stripeRows; } /** * Returns true if rows are highlighted on mouse over. * * @return the track mouse state */ public boolean isTrackMouseOver() { return trackMouseOver; } /** * Returns true if the view is ready. * * @return the view ready state */ public boolean isViewReady() { return viewReady; } @Override @SuppressWarnings("unchecked") public void onComponentEvent(ComponentEvent ce) { super.onComponentEvent(ce); GridEvent ge = (GridEvent) ce; switch (ce.getEventTypeInt()) { case Event.ONCLICK: onClick(ge); break; case Event.ONDBLCLICK: onDoubleClick(ge); break; case Event.ONMOUSEDOWN: onMouseDown(ge); break; } view.handleComponentEvent(ge); } /** * Reconfigures the grid to use a different Store and Column Model. The View * will be bound to the new objects and refreshed. * * @param store the new store * @param cm the new column model */ public void reconfigure(ListStore<M> store, ColumnModel cm) { if (loadMask && rendered) { mask(GXT.MESSAGES.loadMask_msg()); } view.initData(store, cm); this.store = store; this.cm = cm; // rebind the sm setSelectionModel(sm); if (rendered) { view.refresh(true); } } /** * The id of a column in this grid that should expand to fill unused space * (pre-render). This id can not be 0. * * @param autoExpandColumn the auto expand column id */ public void setAutoExpandColumn(String autoExpandColumn) { this.autoExpandColumn = autoExpandColumn; } /** * The maximum width the autoExpandColumn can have (if enabled) (defaults to * 1000, pre-render). * * @param autoExpandMax the auto expand max */ public void setAutoExpandMax(int autoExpandMax) { this.autoExpandMax = autoExpandMax; } /** * The minimum width the autoExpandColumn can have (if enabled)(pre-render). * * @param autoExpandMin the auto expand min width */ public void setAutoExpandMin(int autoExpandMin) { this.autoExpandMin = autoExpandMin; } @Override public void setContextMenu(Menu menu) { // make public super.setContextMenu(menu); } /** * Sets whether columns may be resized (defaults to true). * * @param enableColumnResize true to allow column resizing */ public void setEnableColumnResize(boolean enableColumnResize) { this.enableColumnResize = enableColumnResize; } /** * Sets whether the header should be hidden (defaults to false). * * @param hideHeaders true to hide the header */ public void setHideHeaders(boolean hideHeaders) { this.hideHeaders = hideHeaders; } /** * Sets whether a load mask should be displayed during load operations * (defaults to false). * * @param loadMask true to show a mask */ public void setLoadMask(boolean loadMask) { this.loadMask = loadMask; } /** * The minimum width a column can be resized to (defaults to 25). * * @param minColumnWidth the min column width */ public void setMinColumnWidth(int minColumnWidth) { this.minColumnWidth = minColumnWidth; } /** * Sets the grid selection model. * * @param sm the selection model */ public void setSelectionModel(GridSelectionModel<M> sm) { if (this.sm != null) { this.sm.bindGrid(null); } this.sm = sm; if (sm != null) { sm.bindGrid(this); } } /** * Sets the binder's string provider. * * @param stringProvider the string provider */ public void setStringProvider(ModelStringProvider<M> stringProvider) { this.stringProvider = stringProvider; } /** * True to stripe the rows (defaults to false). * * @param stripeRows true to strip rows */ public void setStripeRows(boolean stripeRows) { this.stripeRows = stripeRows; } /** * True to highlight rows when the mouse is over (defaults to true). * * @param trackMouseOver true to highlight rows on mouse over */ public void setTrackMouseOver(boolean trackMouseOver) { this.trackMouseOver = trackMouseOver; } /** * Sets the view's grid (pre-render). * * @param view the view */ public void setView(GridView view) { this.view = view; // rebind the sm setSelectionModel(sm); } protected void afterRender() { super.afterRender(); view.init(this); view.render(); view.afterRender(); viewReady = true; } @Override protected ComponentEvent createComponentEvent(Event event) { return new GridEvent<M>(this, event); } @Override protected void doAttachChildren() { super.doAttachChildren(); view.doAttach(); } @Override protected void doDetachChildren() { super.doDetachChildren(); view.doDetach(); } protected void onClick(GridEvent<M> e) { if (e.getRowIndex() != -1) { fireEvent(Events.RowClick, e); if (e.getColIndex() != -1) { fireEvent(Events.CellClick, e); } } } protected void onDoubleClick(GridEvent<M> e) { if (e.getRowIndex() != -1) { fireEvent(Events.RowDoubleClick, e); if (e.getColIndex() != -1) { fireEvent(Events.CellDoubleClick, e); } } } protected void onMouseDown(GridEvent<M> e) { int row = e.getRowIndex(); int cell = e.getColIndex(); if (row != -1) { e.setRowIndex(row); e.setColIndex(cell); fireEvent(Events.RowMouseDown, e); if (cell != -1) { fireEvent(Events.CellMouseDown, e); } } } @Override protected void onRender(Element target, int index) { setElement(DOM.createDiv(), target, index); super.onRender(target, index); el().setStyleAttribute("position", "relative"); } @Override protected void onResize(int width, int height) { super.onResize(width, height); if (viewReady) { view.calculateVBar(true); } } @SuppressWarnings("unchecked") protected Cell walkCells(int row, int col, int step, Callback callback, boolean acceptNavs) { boolean first = true; int clen = cm.getColumnCount(); int rlen = store.getCount(); if (step < 0) { if (col < 0) { row--; first = false; } while (row >= 0) { if (!first) { col = clen - 1; } first = false; while (col >= 0) { if (callback.isSelectable(row, col, acceptNavs)) { return new Cell(row, col); } col--; } row--; } } else { if (col >= clen) { row++; first = false; } while (row < rlen) { if (!first) { col = 0; } first = false; while (col < clen) { if (callback.isSelectable(row, col, acceptNavs)) { return new Cell(row, col); } col++; } row++; } } return null; } }