/** * OLAT - Online Learning and Training
* http://www.olat.org *

* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at *

* http://www.apache.org/licenses/LICENSE-2.0 *

* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. *

* Copyright (c) 1999-2006 at Multimedia- & E-Learning Services (MELS),
* University of Zurich, Switzerland. *

*/ 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.StringOutput; import org.olat.core.gui.render.ValidationResult; import org.olat.core.gui.translator.Translator; import org.olat.core.logging.OLATRuntimeException; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; /** * Description:
* 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 COMMAND_ROWACTION_CLICKED_ROWID */ 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 COMMAND_SORTBYCOLUMN */ protected static final String COMMAND_SORTBYCOLUMN = "cid"; /** * Comment for COMMAND_MOVECOLUMN_LEFT */ protected static final String COMMAND_MOVECOLUMN_LEFT = "cl"; /** * Comment for COMMAND_MOVECOLUMN_RIGHT */ protected static final String COMMAND_MOVECOLUMN_RIGHT = "cr"; /** * Comment for COMMAND_PAGEACTION */ protected static final String COMMAND_PAGEACTION = "pg"; /** * Comment for COMMAND_PAGEACTION_SHOWALL */ protected static final String COMMAND_PAGEACTION_SHOWALL = "a"; /** * Comment for COMMAND_PAGEACTION_FORWARD */ protected static final String COMMAND_PAGEACTION_FORWARD = "f"; /** * Comment for COMMAND_PAGEACTION_BACKWARD */ 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(); Tracing.logDebug("sorting time for " + getTableDataModel().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.
* 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
* * @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; } protected int getSortColumn() { return sortColumn; } protected boolean getSortAscending() { return sortAscending; } 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.debug("buildFilteredTableDataModel: tableDataModel.getRowCount()=" + tableDataModel.getRowCount()); if (tableDataModel.getRowCount() > 0) { log.debug("buildFilteredTableDataModel: tableDataModel.getObject(0)=" + tableDataModel.getObject(0)); } for (int row = 0; row < tableDataModel.getRowCount(); row++) { if (matchRowWithSearchString(row, tableSearchString2)) { filteredElementList.add(tableDataModel.getObject(row)); } } log.debug("buildFilteredTableDataModel: unfiltered-row-count=" + tableDataModel.getRowCount() + " filtered-row-count=" + filteredElementList.size()); getFilteredTableDataModel().setObjects(filteredElementList); } /** * Check if the row-value matches with the search-query. * @param row * @param tableSearchString2 * @return */ private boolean matchRowWithSearchString(int row, String tableSearchString2) { 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); // When a CustomCellRenderer exist, use this to render cell-value to String ColumnDescriptor cd = this.getColumnDescriptor(colIndex); if (cd instanceof CustomRenderColumnDescriptor) { CustomCellRenderer customCellRenderer = ((CustomRenderColumnDescriptor)cd).getCustomCellRenderer(); StringOutput sb = new StringOutput(); customCellRenderer.render(sb, null, value, ((CustomRenderColumnDescriptor) cd).getLocale(), cd.getAlignment(), null); value = sb.toString(); } if (value instanceof String) { String valueAsString = (String)value; log.debug("matchRowWithFilter: check " + valueAsString); if (valueAsString.toLowerCase().indexOf(tableSearchString2.toLowerCase()) != -1 ) { log.debug("matchRowWithFilter: found match for row=" + row + " value=" + valueAsString + " with filter=" + tableSearchString2); 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"; } } }