/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Copyright (c) 1999-2006 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <p>
*/
package org.olat.core.gui.components.table;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.ComponentRenderer;
import org.olat.core.gui.control.Event;
import org.olat.core.gui.control.JSAndCSSAdder;
import org.olat.core.gui.render.ValidationResult;
import org.olat.core.gui.translator.Translator;
import org.olat.core.logging.AssertException;
import org.olat.core.logging.OLATRuntimeException;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
/**
* Description: <br>
* a table. 1.) set column descriptors 2.) generate a tabledatamodel with valid
* datas 3.) do table.setTableDataModel(tdm); this inits and sorts the table
*
* @author Felix Jost
*/
public class Table extends Component implements Comparator {
private OLog log = Tracing.createLoggerFor(this.getClass());
private static final ComponentRenderer RENDERER = new TableRenderer();
/**
* TableMultiSelectEvent command identifier.
*/
public static final String COMMAND_MULTISELECT = "ms";
// The following two commands will be submitted traditional style via URLBuilder URIS.
/**
* Table row selection.
*/
public static final String COMMANDLINK_ROWACTION_CLICKED = "r";
/**
* Comment for <code>COMMAND_ROWACTION_CLICKED_ROWID</code>
*/
protected static final String COMMANDLINK_ROWACTION_ID = "p";
// The following commands will be submitted via hidden form parameters.
// The commands are internal to the table and affect functionality such as sorting and pageing.
// The two ids formCmd and formParam are the hidden fields used by the form to submit the relevant actions.
private static final String FORM_CMD = "cmd";
private static final String FORM_PARAM = "param";
/**
* Comment for <code>COMMAND_SORTBYCOLUMN</code>
*/
protected static final String COMMAND_SORTBYCOLUMN = "cid";
/**
* Comment for <code>COMMAND_MOVECOLUMN_LEFT</code>
*/
protected static final String COMMAND_MOVECOLUMN_LEFT = "cl";
/**
* Comment for <code>COMMAND_MOVECOLUMN_RIGHT</code>
*/
protected static final String COMMAND_MOVECOLUMN_RIGHT = "cr";
/**
* Comment for <code>COMMAND_PAGEACTION</code>
*/
protected static final String COMMAND_PAGEACTION = "pg";
/**
* Comment for <code>COMMAND_PAGEACTION_SHOWALL</code>
*/
protected static final String COMMAND_PAGEACTION_SHOWALL = "a";
/**
* Comment for <code>COMMAND_PAGEACTION_FORWARD</code>
*/
protected static final String COMMAND_PAGEACTION_FORWARD = "f";
/**
* Comment for <code>COMMAND_PAGEACTION_BACKWARD</code>
*/
protected static final String COMMAND_PAGEACTION_BACKWARD = "b";
protected static final String COMMAND_SHOW_PAGES = "s_p";
// order of left-to-right presentation of Columns (visible columndescriptors):
// list of columndescriptors
List columnOrder; // default visibility to improve speed, not private
// all column descriptors whether visible or not
List allCDs; // default visibility to improve speed, not private
private TableDataModel tableDataModel;
// DO NOT REFERENCE filteredTableDataModel directly, use always getFilteredTableDataModel() because lazy init!
private TableDataModel filteredTableDataModel;
private List sorter;
private int sortColumn = 0;
private ColumnDescriptor currentSortingCd;
private boolean sortAscending = true;
// config
private boolean multiSelect = false;
private boolean selectedRowUnselectable = false;
private boolean sortingEnabled = true;
private boolean columnMovingOffered = true;
private boolean displayTableHeader = true;
private boolean pageingEnabled = true;
private Integer currentPageId;
private int resultsPerPage;
private boolean isShowAllSelected;
private List multiSelectActionsI18nKeys = new ArrayList();
private List multiSelectActionsIdentifiers = new ArrayList();
private BitSet multiSelectSelectedRows = new BitSet();
private BitSet multiSelectReadonlyRows = new BitSet();
int selectedRowId;
boolean markRow = false;
boolean markSort = false;
boolean markMoveR = false;
boolean markMoveL = false;
boolean markPageAction = false;
String markValue = null;
int markedColumn;
boolean markPageShowAll;
boolean markPageForward;
boolean markPageBackward;
int markActivatePageX;
int markSelectedRowId;
String markActionId;
private boolean enableShowAllLinkValue = true;
private String tableSearchString;
/**
* Constructor for a table. The table is preconfigured with the following
* options: downloadOffered=true, sortingEnabled=true,
* columnMovingOffered=true, displayTableHeader=true, displayRowCount=true
*
* @param name
*/
protected Table(String name, Translator translator) {
super(name, translator);
columnOrder = new ArrayList(5);
allCDs = new ArrayList(5);
sorter = new ArrayList(20);
selectedRowId = -1;
currentPageId = new Integer(1); // default value
resultsPerPage = 20; // default value
}
/**
* @param column
* @return Column descriptor of given column
*/
protected ColumnDescriptor getColumnDescriptor(int column) {
ColumnDescriptor cd = (ColumnDescriptor) columnOrder.get(column);
return cd;
}
/**
* @return Column descriptor of currently sorted column
*/
protected ColumnDescriptor getCurrentlySortedColumnDescriptor() {
return getColumnDescriptor(sortColumn);
}
/**
*
*/
protected void modelChanged() {
// we got a new TableDataModel, so we need to prepare the sorting
int rows = getRowCount();
selectedRowId = -1; // no selection anymore
sorter = new ArrayList();
for (int i = 0; i < rows; i++) {
sorter.add(new Integer(i));
}
// notify all ColumnDescriptors so that they get a chance to presort/cache
// sorting before
// the comparator calls start
int cdcnt = getColumnCount();
for (int i = 0; i < cdcnt; i++) {
ColumnDescriptor cd = getColumnDescriptor(i);
cd.modelChanged();
}
// now sort
resort();
updatePageing(getCurrentPageId());
// do not reset pageing to first page - else cannot keep the page selection when item selected (see OLAT-1340)
/*if (pageingEnabled) {
currentPageId = new Integer(1);
}*/
// Reset multi selected rows
// Best would be to remove the unnecessary bits, but how can we know if
// a row has been added or deleted? Most save action is to clear the selections
// and start fresh. But we will loose the selections this way.
// Any better ideas?
multiSelectSelectedRows = new BitSet();
}
/**
* serves a purpose: it maps from the rowid in the gui (first row = 0, second =
* 1 and so on) to the corresponding row in the tabledatamodel
* getSortedRow(guirow) is used by the columnDescriptors: public String
* getRenderValue(int row).. to determine the row in the model they have to
* return and the tablerenderer
*
* @param originalRow
* @return integer representing the row id after sorting
*/
public int getSortedRow(int originalRow) {
Integer i = (Integer) sorter.get(originalRow);
return i.intValue();
}
/**
* @return integer representing the number of columns in the table
*/
protected int getColumnCount() {
return columnOrder.size();
}
/**
* @return integer representing the number of rows in the table
*/
protected int getRowCount() {
if (isTableFiltered()) {
return getFilteredTableDataModel().getRowCount();
} else {
return tableDataModel.getRowCount();
}
}
/**
* @param position The position of the column descriptor. Set to -1 to add this CD at the end.
* @param visible
* @param cd
*/
protected void addColumnDescriptor(ColumnDescriptor cd, int position, boolean visible) {
cd.setTable(this);
if (position != -1)
allCDs.add(position, cd);
else
allCDs.add(cd);
if (visible) {
if (position != -1)
columnOrder.add(position, cd);
else
columnOrder.add(cd);
}
}
// public boolean isColumnDescriptorVisible()
/**
* @param cd
*/
protected void addColumnDescriptor(ColumnDescriptor cd) {
addColumnDescriptor(cd, -1, true);
}
/**
* Remove a column descriptor.
*
* @param position
*/
protected void removeColumnDescriptor(int position) {
allCDs.remove(position);
columnOrder.remove(position);
if (sortColumn >= allCDs.size())
sortColumn = 0;
}
/**
* @see org.olat.core.gui.components.Component#dispatchRequest(org.olat.core.gui.UserRequest)
*/
protected void doDispatchRequest(UserRequest ureq) {
String formCmd = ureq.getParameter(FORM_CMD);
String formParam = ureq.getParameter(FORM_PARAM);
String rowAction = ureq.getParameter(COMMANDLINK_ROWACTION_CLICKED);
// translate from ureq param, replay can then reuse code
int cmd = -1;
String value1 = formParam;
String value2 = null;
if (formCmd != null && formCmd.length() > 0) {
// this is an internal command submitted by a form-submit()
// first update the multiselect state
updateMultiSelectState(ureq);
// then fetch the internal command to be processed
if (formCmd.equals(COMMAND_SORTBYCOLUMN)) {
cmd = TableReplayableEvent.SORT;
} else if (formCmd.equals(COMMAND_MOVECOLUMN_RIGHT)) {
cmd = TableReplayableEvent.MOVE_R;
} else if (formCmd.equals(COMMAND_MOVECOLUMN_LEFT)) {
cmd = TableReplayableEvent.MOVE_L;
} else if (formCmd.equals(COMMAND_PAGEACTION)) {
cmd = TableReplayableEvent.PAGE_ACTION;
}
} else if (rowAction != null) {
// this is a row action clicked by the user. no form is submitted, so we don't evaluate any columns.
cmd = TableReplayableEvent.ROW_ACTION;
value1 = rowAction;
value2 = ureq.getParameter(COMMANDLINK_ROWACTION_ID);
// sanity check
int rowid = Integer.parseInt(value1);
int actualrows = getTableDataModel().getRowCount();
if (rowid < 0 || rowid >= actualrows) throw new RuntimeException("Rowid out of range:" + rowid);
} else {
// check for multiselect actions
for (Iterator iter = multiSelectActionsIdentifiers.iterator(); iter.hasNext();) {
String actionIdentifier = (String)iter.next();
if (ureq.getParameter(actionIdentifier) != null) {
// get the multiselect command
cmd = TableReplayableEvent.MULTISELECT_ACTION;
value1 = actionIdentifier;
// update multiselect state
updateMultiSelectState(ureq);
break;
}
}
}
dispatchRequest(ureq, cmd, value1, value2);
}
/**
* @param ureq
* @param theCmd
* @param theValue
*/
private void dispatchRequest(UserRequest ureq, int cmd, String value1, String value2) {
String replayTitle = null;
Map recMap = null;
int recCmd = -1;
if (cmd == TableReplayableEvent.SORT) {
// if sorting command, resort
int oldSortColumn = sortColumn;
sortColumn = Integer.parseInt(value1);
if (oldSortColumn == sortColumn) { // click the same column again, change
// sort order
sortAscending = !sortAscending;
} else { // new column, always sort ascending first
sortAscending = true;
}
setDirty(true);
resort();
} else if (cmd == TableReplayableEvent.MOVE_R) { // move column right
int col = Integer.parseInt(value1);
int swapCol = (col + 1) % (getColumnCount());
ColumnDescriptor cdMove = getColumnDescriptor(col);
ColumnDescriptor cdSwap = getColumnDescriptor(swapCol);
columnOrder.set(col, cdSwap);
columnOrder.set(swapCol, cdMove);
if (col == sortColumn) { // if the moved column was sorted, update the
// sortedcolumn info
sortColumn = swapCol;
} else if (swapCol == sortColumn) {
sortColumn = col;
}
setDirty(true);
} else if (cmd == TableReplayableEvent.MOVE_L) { // move column left
int col = Integer.parseInt(value1);
int swapCol = (col - 1) % (getColumnCount());
ColumnDescriptor cdMove = getColumnDescriptor(col);
ColumnDescriptor cdSwap = getColumnDescriptor(swapCol);
columnOrder.set(col, cdSwap);
columnOrder.set(swapCol, cdMove);
if (col == sortColumn) { // if the moved column was sorted, update the
// sortedcolumn info
sortColumn = swapCol;
} else if (swapCol == sortColumn) {
sortColumn = col;
}
setDirty(true);
} else if (cmd == TableReplayableEvent.PAGE_ACTION) {
if (value1.equals(COMMAND_PAGEACTION_SHOWALL)) {
//updatePageing(null); (see OLAT-1340)
setShowAllSelected(true);
fireEvent(ureq, new Event(COMMAND_PAGEACTION_SHOWALL));
setDirty(true);
} else if (value1.equals(COMMAND_PAGEACTION_FORWARD)) {
if (currentPageId != null) {
updatePageing(new Integer(currentPageId.intValue() + 1));
setDirty(true);
}
} else if (value1.equals(COMMAND_PAGEACTION_BACKWARD)) {
if (currentPageId != null) {
updatePageing(new Integer(currentPageId.intValue() - 1));
setDirty(true);
}
} else if (value1.equals(COMMAND_SHOW_PAGES)) {
setShowAllSelected(false);
fireEvent(ureq, new Event(COMMAND_SHOW_PAGES));
if (currentPageId != null) {
updatePageing(new Integer(currentPageId.intValue()));
}else {
updatePageing(new Integer(1));
}
setDirty(true);
} else {
updatePageing(new Integer(Integer.parseInt(value1)));
setDirty(true);
}
} else if (cmd == TableReplayableEvent.ROW_ACTION) {
selectedRowId = Integer.parseInt(value1);
String actionId = value2;
//setDirty(true); commented as timestamp was consumed in AJAX mode: see OLAT-2007
// create and add replay event
fireEvent(ureq, new TableEvent(COMMANDLINK_ROWACTION_CLICKED, selectedRowId, actionId));
return;
} else if (cmd == TableReplayableEvent.MULTISELECT_ACTION) {
setDirty(true);
fireEvent(ureq, new TableMultiSelectEvent(COMMAND_MULTISELECT, value1, getMultiSelectSelectedRows()));
}
}
/**
* Updates the state of multi selects in the table.
* The state is saved in a BitSet (each bit representing a
* column within the tablemodel.
*
*/
private void updateMultiSelectState(UserRequest ureq) {
String[] sRowIds = ureq.getHttpReq().getParameterValues(TableRenderer.TABLE_MULTISELECT_GROUP);
if (sRowIds == null) {
multiSelectSelectedRows = new BitSet(); //if all deselected create new multiSelectSelectedRows
return;
}
List rowIds = new ArrayList();
for (int i = 0; i < sRowIds.length; i++) {
String sRowId = sRowIds[i];
try {
rowIds.add(new Integer(sRowId));
} catch (NumberFormatException nfe) {
throw new OLATRuntimeException("Invalid rowID submitted as table multiselect parameter", nfe);
}
}
int rows = getRowCount();
int startRowId = 0;
int endRowId = rows;
// initalize pageing
if (isPageingEnabled() && currentPageId != null && !isShowAllSelected()) {
startRowId = ((currentPageId.intValue() - 1) * resultsPerPage);
endRowId = startRowId + resultsPerPage;
if (endRowId > rows) endRowId = rows;
} else {
startRowId = 0;
endRowId = rows;
}
// walk through all the rows and se if the row is selected this time.
// we need this because user might have unchecked a row.
for (int i = startRowId; i < endRowId; i++) {
Integer sortedRow = new Integer(getSortedRow(i));
if (rowIds.contains(sortedRow)) {
rowIds.remove(sortedRow);
multiSelectSelectedRows.set(sortedRow.intValue());
} else {
multiSelectSelectedRows.clear(sortedRow.intValue());
}
}
}
/**
* @param rowid
*/
private String getRowHash(int rowid) {
StringBuilder rowHash = new StringBuilder();
for (Iterator iter = allCDs.iterator(); iter.hasNext();) {
ColumnDescriptor cd = (ColumnDescriptor) iter.next();
rowHash.append(".").append(cd.toString(rowid));
}
return rowHash.toString();
}
/**
* Sets the tableDataModel. IMPORTANT: Once a tableDataModel is set, it is
* assumed to remain constant in its data & row & colcount. Otherwise a
* modelChanged has to be called
*
* @param tableDataModel The tableDataModel to set
*/
protected void setTableDataModel(TableDataModel tableDataModel) {
this.tableDataModel = tableDataModel;
this.filteredTableDataModel = null; // lazy init
// modelChanged(); now called from the controller
}
/**
* @return TableDataModel
*/
public TableDataModel getTableDataModel() {
if (isTableFiltered()) {
return getFilteredTableDataModel();
} else {
return tableDataModel;
}
}
/**
* @return filtered TableDataModel
*/
public TableDataModel getUnfilteredTableDataModel() {
return tableDataModel;
}
/**
* @return filtered TableDataModel
*/
public TableDataModel getFilteredTableDataModel() {
if (filteredTableDataModel == null) {
this.filteredTableDataModel = (TableDataModel)tableDataModel.createCopyWithEmptyList();
}
return filteredTableDataModel;
}
protected void resort() {
if (isSortingEnabled()) {
currentSortingCd = getColumnDescriptor(sortColumn); // we sort after this
// column descriptor
// notify all nonactive ColumnDescriptors about their state
int cdcnt = getColumnCount();
for (int i = 0; i < cdcnt; i++) {
ColumnDescriptor cd = getColumnDescriptor(i);
if (cd != currentSortingCd) cd.otherColumnDescriptorSorted();
}
if (currentSortingCd == null) throw new RuntimeException("cannot find columndesc for column " + sortColumn
+ " in sorting process, maybe you have set the tabledatamodel before the columndescriptors?");
currentSortingCd.sortingAboutToStart();
long start = 0, stop;
boolean logDebug = Tracing.isDebugEnabled(Table.class);
if (logDebug) start = System.currentTimeMillis();
Collections.sort(sorter, this);
if (logDebug) {
stop = System.currentTimeMillis();
TableDataModel model = getTableDataModel();
Tracing.logDebug("sorting time for " + (model==null ? "null" : model.getRowCount()) + " rows:" + (stop - start) + " ms", Table.class);
}
// do not reset paging to first page (see OLAT-1340)
//if (currentPageId != null) currentPageId = new Integer(1);
}
}
/**
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
public int compare(Object a, Object b) {
// the objects to come in are Integers of the sorter List, meaning the
// original row in the datatablemodel
int rowa = ((Integer) a).intValue();
int rowb = ((Integer) b).intValue();
return (sortAscending ? currentSortingCd.compareTo(rowa, rowb) : currentSortingCd.compareTo(rowb, rowa));
}
/**
* * used by renderer only
*
* @return boolean
*/
public boolean isSortAscending() {
return sortAscending;
}
/**
* Sets the sortColumn.
*
* @param sortColumn The sortColumn to set
* @param isSortAscending true: sorting ascending order
*/
protected void setSortColumn(int sortColumn, boolean isSortAscending) {
this.sortColumn = sortColumn;
this.sortAscending = isSortAscending;
}
/**
* @return boolean
*/
protected boolean isSelectedRowUnselectable() {
return selectedRowUnselectable;
}
/**
* Sets the selectedRowUnselectable.
*
* @param selectedRowUnselectable The selectedRowUnselectable to set
*/
protected void setSelectedRowUnselectable(boolean selectedRowUnselectable) {
this.selectedRowUnselectable = selectedRowUnselectable;
}
/**
* @return int
*/
protected int getSelectedRowId() {
return selectedRowId;
}
/**
* Sets the selectedRowId.
*
* @param selectedRowId The selectedRowId to set
*/
protected void setSelectedRowId(int selectedRowId) {
this.selectedRowId = selectedRowId;
}
/**
* @return true when data can be and should be sorted, false for data that is
* not sortable
*/
public boolean isSortingEnabled() {
return sortingEnabled;
}
/**
* Set table configuration: should table be sortable and be sorted when adding
* a new table data model?
*
* @param sortingEnabled true: allow table sorting, false: never sort table,
* not even when adding a new table data model.
*/
protected void setSortingEnabled(boolean sortingEnabled) {
this.sortingEnabled = sortingEnabled;
}
/**
* @return true when columns can be moved left/right
*/
protected boolean isColumnMovingOffered() {
return columnMovingOffered;
}
/**
* Set column moving configuration
*
* @param columnMovingOffered
*/
protected void setColumnMovingOffered(boolean columnMovingOffered) {
this.columnMovingOffered = columnMovingOffered;
}
/**
* @return true: table will render header, false: table has no headers
*/
protected boolean isDisplayTableHeader() {
return displayTableHeader;
}
/**
* Set the table header configuration
*
* @param displayTableHeader
*/
protected void setDisplayTableHeader(boolean displayTableHeader) {
this.displayTableHeader = displayTableHeader;
}
/**
* @param cd
* @return true if the columndescriptor is visible
*/
protected boolean isColumnDescriptorVisible(ColumnDescriptor cd) {
return columnOrder.contains(cd);
}
/**
* @return a tabledatamodel for a choice
*/
protected TableDataModel createChoiceTableDataModel() {
return new ChoiceTableDataModel(this.isMultiSelect(), this.allCDs, this.columnOrder, this.getTranslator());
}
/**
* @param selRows
*/
protected void updateConfiguredRows(List selRows) {
setDirty(true);
columnOrder.clear();
for (Iterator it_sel = selRows.iterator(); it_sel.hasNext();) {
int pos = ((Integer)it_sel.next()).intValue();
// if multiselect, skip the first cd (which is the multiselect CD)
if (isMultiSelect()) pos += 1;
columnOrder.add(allCDs.get(pos));
}
// make sure sorting is smooth in all cases
if (isMultiSelect()) {
// if multiselect, add the multiselect CD at the beginning
MultiSelectColumnDescriptor mscd = new MultiSelectColumnDescriptor();
mscd.setTable(this);
columnOrder.add(0, mscd);
setSortColumn(1, isSortAscending());
} else {
setSortColumn(0, isSortAscending());
}
resort();
}
/**
* @param selRows
* @return true if there is at least one sortable row in the list of all
* columndescriptors (both visible and invisible)
*/
public boolean isSortableColumnIn(List selRows) {
for (Iterator it_selrows = selRows.iterator(); it_selrows.hasNext();) {
Integer posI = (Integer) it_selrows.next();
ColumnDescriptor cd = (ColumnDescriptor) allCDs.get(posI.intValue());
if (cd.isSortingAllowed()) return true;
}
return false;
}
/**
* @param newPageId new id used as active page
*/
protected void updatePageing(Integer newPageId) {
if (newPageId == null) {
this.currentPageId = null;
} else {
if (newPageId.intValue() < 1) {
this.currentPageId = new Integer(1);
} else {
this.currentPageId = newPageId;
if(tableDataModel!=null) {
int maxPageNumber = (int)(tableDataModel.getRowCount()/getResultsPerPage());
if(tableDataModel.getRowCount()%getResultsPerPage() > 0) {
maxPageNumber++;
} while(currentPageId>maxPageNumber && currentPageId>1) {
currentPageId--;
}
}
}
}
}
/**
* @param enabledFlag
*/
protected void setPageingEnabled(boolean enabledFlag) {
this.pageingEnabled = enabledFlag;
}
/**
* @return boolean
*/
protected boolean isPageingEnabled() {
return pageingEnabled;
}
/**
* @return Integer current page position
*/
protected Integer getCurrentPageId() {
return currentPageId;
}
/**
* @return int number of results per page
*/
protected int getResultsPerPage() {
return resultsPerPage;
}
/**
* @param resultsPerPage number of results per page
*/
protected void setResultsPerPage(int resultsPerPage) {
this.resultsPerPage = resultsPerPage;
}
public ComponentRenderer getHTMLRendererSingleton() {
return RENDERER;
}
/**
* while recording clicks in a table a specific row was selected because of
* the values present in the table. The id of the row is not to be constant
* for later replaying, hence the need for another identification possibility.
* The rowHash represents the concatenated values of the clicked row and
* hopefully they create a "unique" identifier within the row.<br>
* Refinding such a recorded click demands in the worst case a fulltable scan.
* Two heuristics are used to speed up: - check the row at the rowId first -
* let delta := | current number of rows - rowCnt | then search first within
* rowId - delta and rowId + delta. - if not found, search full table<br>
*
* @param rowId id of the row at recording time
* @param rowCnt number of rows in table at recording time
* @param rowHash pseudo unique key of a table entry
* @return
*/
private int findByRowHash(int rowId, int rowCnt, String rowHash) {
/*
* check first if the row is still at the same place
*/
String tmpHash = getRowHash(rowId);
if (rowHash.equals(tmpHash)) { return rowId; }
/*
* check if it is within rowId - delta and rowId + delta
*/
int actualrows = getTableDataModel().getRowCount();
int delta = Math.abs(actualrows - rowCnt);
int lower = rowId - delta;
int upper = rowId + delta;
// check calculated indexes.
lower = lower > -1 ? lower : 0;
upper = upper < actualrows ? upper : actualrows - 1;
for (int i = 0; lower + i <= upper; i++) {
tmpHash = getRowHash(lower + i);
if (rowHash.equals(tmpHash)) { return lower + i; }
}
/*
* no heuristics helped, full table scan, decide whether to start search
* from head or from tail depending on where the rowId at recording time was
* in the first half, respective second half.
*/
boolean searchFromHead = rowId < (rowCnt / 2);
if (searchFromHead) {
for (int i = 0; i < actualrows; i++) {
tmpHash = getRowHash(i);
if (rowHash.equals(tmpHash)) { return i; }
}
} else {
for (int i = actualrows - 1; i > -1; i--) {
tmpHash = getRowHash(i);
if (rowHash.equals(tmpHash)) { return i; }
}
}
/*
* not found
*/
return -1;
}
/**
*
*/
private void clearAllVisualMarkers() {
this.markRow = false;
this.markSort = false;
this.markMoveR = false;
this.markMoveL = false;
this.markPageAction = false;
this.markedColumn = -1;
this.markValue = null;
this.markSelectedRowId = -1;
this.markActionId = null;
this.markPageBackward = false;
this.markPageForward = false;
this.markPageShowAll = false;
}
protected boolean isMultiSelect() {
return multiSelect;
}
protected void setMultiSelect(boolean multiSelect) {
if (!this.multiSelect && multiSelect) {
// state change: from non-multiselect to multiselect: add extra checkbox column
// add a CD to render the checkboxes
addColumnDescriptor(new MultiSelectColumnDescriptor(), 0, true);
// adjust sort column
setSortColumn(sortColumn + 1, isSortAscending());
} else if (this.multiSelect && !multiSelect) {
// state change: from multiselect to non-multiselect: remove extra checkbox column
// add a CD to render the checkboxes
removeColumnDescriptor(0);
// adjust sort column
setSortColumn(sortColumn - 1, isSortAscending());
}
// only update after state change checks (see above) are through
this.multiSelect = multiSelect;
}
protected void addMultiSelectAction(String actionKeyi18n, String actionIdentifier) {
multiSelectActionsI18nKeys.add(actionKeyi18n);
multiSelectActionsIdentifiers.add(actionIdentifier);
}
protected List getMultiSelectActionsI18nKeys() {
return multiSelectActionsI18nKeys;
}
protected List getMultiSelectActionsIdentifiers() {
return multiSelectActionsIdentifiers;
}
protected BitSet getMultiSelectSelectedRows() {
return multiSelectSelectedRows;
}
public void validate(UserRequest ureq, ValidationResult vr) {
super.validate(ureq, vr);
// include needed css and js libs
JSAndCSSAdder jsa = vr.getJsAndCSSAdder();
jsa.addRequiredJsFile(Table.class, "js/table.js");
}
public boolean isShowAllSelected() {
return isShowAllSelected;
}
public void setShowAllSelected(boolean isShowAllSelected) {
this.isShowAllSelected = isShowAllSelected;
}
public void enableShowAllLink(boolean enableShowAllLinkValue) {
this.enableShowAllLinkValue = enableShowAllLinkValue;
}
public boolean isShowAllLinkEnabled() {
return enableShowAllLinkValue;
}
protected void setMultiSelectSelectedAt(int row, boolean selected) {
multiSelectSelectedRows.set(row, selected);
}
protected void setMultiSelectReadonlyAt(int row, boolean readonly) {
multiSelectReadonlyRows.set(row, readonly);
}
protected BitSet getMultiSelectReadonlyRows() {
return multiSelectReadonlyRows;
}
public int getUnfilteredRowCount() {
return tableDataModel.getRowCount();
}
public void setSearchString(String tableSearchString) {
this.tableSearchString = tableSearchString;
if (isTableFiltered()) {
buildFilteredTableDataModel(tableSearchString);
}
}
private void buildFilteredTableDataModel(String tableSearchString2) {
ArrayList filteredElementList = new ArrayList();
log.info("buildFilteredTableDataModel: tableDataModel.getRowCount()=" + tableDataModel.getRowCount());
if (tableDataModel.getRowCount() > 0) {
log.info("buildFilteredTableDataModel: tableDataModel.getObject(0)=" + tableDataModel.getObject(0));
}
for (int row = 0; row < tableDataModel.getRowCount(); row++) {
if (matchRowWithSearchString(row)) {
filteredElementList.add(tableDataModel.getObject(row));
}
}
log.info("buildFilteredTableDataModel: unfiltered-row-count=" + tableDataModel.getRowCount() + " filtered-row-count=" + filteredElementList.size());
getFilteredTableDataModel().setObjects(filteredElementList);
}
private boolean matchRowWithSearchString(int row) {
log.debug("matchRowWithFilter: row=" + row + " tableFilterString=" + tableSearchString);
if ( !isTableFiltered() ) {
return true;
}
// loop over all columns
for (int colIndex = 0; colIndex < getColumnCount(); colIndex++) {
Object value = tableDataModel.getValueAt(row, colIndex);
if (value instanceof String) {
String valueAsString = (String)value;
log.debug("matchRowWithFilter: check " + valueAsString);
if (valueAsString.toLowerCase().indexOf(tableSearchString.toLowerCase()) != -1 ) {
log.debug("matchRowWithFilter: found match for row=" + row + " value=" + valueAsString + " with filter=" + tableSearchString);
return true;
}
}
}
return false;
}
public String getSearchString( ) {
return tableSearchString;
}
public boolean isTableFiltered() {
return tableSearchString != null;
}
}
class ChoiceTableDataModel extends BaseTableDataModelWithoutFilter {
private boolean isMultiSelect;
private List allCDs;
private List columnOrder;
private Translator translator;
protected ChoiceTableDataModel(boolean isMultiSelect, List allCDs, List columnOrder, Translator translator) {
this.isMultiSelect = isMultiSelect;
this.allCDs = allCDs;
this.columnOrder = columnOrder;
this.translator = translator;
}
/**
* @see org.olat.core.gui.components.table.TableDataModel#getColumnCount()
*/
public int getColumnCount() {
return 2;
}
/**
* @see org.olat.core.gui.components.table.TableDataModel#getRowCount()
*/
public int getRowCount() {
// if this is a multiselect table, we do not want the checkboxes of
// the multiselect to be disabled. therefore we simply exclude the entire
// checkbox row (which is at the very beginning of the CD array).
if (isMultiSelect)
return allCDs.size() - 1;
else return allCDs.size();
}
/**
* @see org.olat.core.gui.components.table.TableDataModel#getValueAt(int,
* int)
*/
public Object getValueAt(int row, int col) {
ColumnDescriptor cd = (ColumnDescriptor) allCDs.get(isMultiSelect? (row + 1): row);
switch (col) {
case 0: // on/off indicator; true if column is visible
return (columnOrder.contains(cd) ? Boolean.TRUE : Boolean.FALSE);
case 1: // name of columndescriptor
return cd.translateHeaderKey() ? translator.translate(cd.getHeaderKey()) : cd.getHeaderKey();
default:
return "ERROR";
}
}
}