Friday, August 21, 2009

Multi cell selection in a row using shift and control key – grid GXT

This post is an extension to this previous post. Code in the previous post is used to multi select cell in a row using the shift keys. It is updated to add functionality to select cells in a particular row using control key and also highlight the row that we are currently working on.

import java.util.ArrayList;
import java.util.List;
import com.extjs.gxt.ui.client.core.El;
import com.extjs.gxt.ui.client.data.ModelData;
import com.extjs.gxt.ui.client.event.GridEvent;
import com.extjs.gxt.ui.client.widget.grid.CellSelectionModel;
import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
/**
 * This class is an extension of CellSelectionModel. 
 * The CellSelectionModel allows us to select only one cell at a particular time. 
 * This Class allows us to select multiple cells in a particular row in a grid.
 * Multi select is performed by pressing the shift key with the mouse click
 *  
 * @author Ravikanth Kolli
 * @version 1.0
 * 
 * 
 * @param <M>
 */
public class RowCellSelectionModel<M extends ModelData> extends CellSelectionModel<M> { 
int lastSelectedRow = -1;
int lastSelectedCol = -1;
private CellSelection selected ;
List<CellSelection> cellsSelected = new ArrayList<CellSelection>();
/**
 * Overriding the handleMouseDown event.
 * MultiSelect is done by pressing the shiftkey 
 */
@Override
protected void handleMouseDown(GridEvent e) {
super.handleMouseDown(e);
if(lastSelectedRow == -1)
{
selected = new CellSelection(listStore.getAt(e.getRowIndex()),e.getRowIndex(),e.getColIndex());
cellsSelected.add(selected);
lastSelectedRow = e.getRowIndex();
lastSelectedCol = e.getColIndex();
}else if (lastSelectedRow == e.getRowIndex() && (e.isShiftKey() || e.isControlKey())) {
// control key press adding or removing cell from the cellsSelected list. 
if(e.isShiftKey())
{
if(cellsSelected.size() > 0)
El.fly(grid.getView().getRow(cellsSelected.get(0).row)).removeStyleName("x-grid3-row-selected");
cellsSelected.clear();
for (int i = 1 ; i < e.getGrid().getColumnModel().getColumnCount(); i++) {
Element cell = grid.getView().getCell(lastSelectedRow, i);
if (cell != null) {
El.fly(cell).removeStyleName("x-grid3-cell-selected");
}
}
if(lastSelectedCol > e.getColIndex()){
for (int iter = e.getColIndex(); iter < lastSelectedCol+1 ; iter++)
{
selected = new CellSelection(listStore.getAt(e.getRowIndex()),e.getRowIndex(),iter);
cellsSelected.add(selected);
}
lastSelectedRow = e.getRowIndex();
}else
{
for (int iter = lastSelectedCol; iter < e.getColIndex() + 1 ; iter++)
{
selected = new CellSelection(listStore.getAt(e.getRowIndex()),e.getRowIndex(),iter);
cellsSelected.add(selected);
}
lastSelectedRow  = e.getRowIndex();
} 
}
// control key press adding or removing cell from the cellsSelected list. 
else if(e.isControlKey())
{
if(cellsSelected.size() > 0)
El.fly(grid.getView().getRow(cellsSelected.get(0).row)).removeStyleName("x-grid3-row-selected");
boolean isSelected = false;
for (int i =0 ; i < cellsSelected.size();i++)
{
El.fly(grid.getView().getCell(cellsSelected.get(i).row, cellsSelected.get(i).cell)).removeStyleName("x-grid3-cell-selected");
if(cellsSelected.get(i).row == e.getRowIndex() && cellsSelected.get(i).cell == e.getColIndex())
{
isSelected = true;
cellsSelected.remove(i);
}
}
if(!isSelected)
{
selected = new CellSelection(listStore.getAt(e.getRowIndex()), e.getRowIndex(), e.getColIndex());
cellsSelected.add(selected);
}
lastSelectedRow = e.getRowIndex();
}
}
else
{
if(!e.isRightClick() 
||  cellsSelected.size() == 0
|| !(cellsSelected.get(0).row == e.getRowIndex() && cellsSelected.get(0).cell <= e.getColIndex() && e.getColIndex() <= cellsSelected.get(cellsSelected.size() - 1).cell) 
||  cellsSelected.get(0).row != e.getRowIndex()){
if(cellsSelected.size() > 0)
El.fly(grid.getView().getRow(cellsSelected.get(0).row)).removeStyleName("x-grid3-row-selected");
cellsSelected.clear();
for (int i = 1 ; i < e.getGrid().getColumnModel().getColumnCount(); i++)
{
Element cell = grid.getView().getCell(lastSelectedRow, i);
if (cell != null) {
El.fly(cell).removeStyleName("x-grid3-cell-selected");
}
}
selected = new CellSelection(listStore.getAt(e.getRowIndex()),e.getRowIndex(),e.getColIndex());
cellsSelected.add(selected);
lastSelectedRow = e.getRowIndex();
lastSelectedCol = e.getColIndex();
}
}
/**
 * Add the cell selected style after all the events are handled
 */
DeferredCommand.addCommand(new Command() {
public void execute() {
      if(cellsSelected.size() > 0)
       {
El.fly(grid.getView().getRow(cellsSelected.get(0).row)).addStyleName("x-grid3-row-selected");
       }
for (int iter = 0 ; iter < cellsSelected.size() ; iter++) {
Element element = grid.getView().getCell(cellsSelected.get(iter).row,cellsSelected.get(iter).cell);
if (element != null) {
El.fly(element).addStyleName("x-grid3-cell-selected");
}
El.fly(grid.getView().getHeaderCell(cellsSelected.get(iter).cell)).setStyleName("x-setItalics");
}     
}
});
}
/**
 * Method to get the list of all the selected cells at a particular time
 * @return list of selected CellSelections.
 */
public List<CellSelection> getSelectedCells()
{
return cellsSelected;
}
/**
 * clear the selections in cellsSelected
 */
public void clearSelections()
{
cellsSelected.clear();
}
}

Thursday, August 13, 2009

Improving grid performance of an application GXT

For over a month now, I have been working on improving the performance of my application and get feedback from the clients for areas of improvement. My application was running really slow and the reason

1) IE being the only browser that our company supports, is pretty bad in handling white spaces(we do a lot of server side processing in ColdFusion which comes up with a lot white spaces)

Solution: shrink the ColdFusion file into a single line

2) JSONReader is taking a whole lot of time to extract the data and put it in a store.

Solution: After a lot of debugging, I figured out that “com.google.gwt.json.client.JSONObject” that is used in the JSONReader uses a HashMap to store the JSONValue and the get method of HashMap it the most time consuming.

GXT has FastMap which is pretty fast than HashMap. I try to override the mapping part of the JSONObject and try to generate my custom map using FastMap.

public Map<String, Object> decode(JSONObject jso) {
Map<String, Object> map = new FastMap<Object>();
for (String key : jso.keySet()) {
JSONValue j = jso.get(key);
map.put(key, j.isString().stringValue());
}
return map;
}

Whenever I try to get a single JSONValue i use the map to get the data. I tested it and is about 50-60% faster than the previous.

3) Grid rendering also takes a long time which increases with the number of rows.

Solution: I was going through the GXT blog and read about the buffered view for a grid, using which we can render only the rows in the grid that are visible, and each time the scroll bar is used previous rows are cleared from the DOM and new rows are added. This makes the rendering pretty fast and also the speed of the editing features in the grid is increased.

  1: BufferView view = new BufferView();
  2: view.setScrollDelay(10);
  3: view.setRowHeight(28);
  4: grid.setView(view);
The row height has to specified in the view and I think it is needed to decide the number of rows to be displayed.