/**
* 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.BitSet;
import java.util.List;
import org.apache.commons.lang.StringEscapeUtils;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.ComponentRenderer;
import org.olat.core.gui.control.winmgr.AJAXFlags;
import org.olat.core.gui.render.RenderResult;
import org.olat.core.gui.render.Renderer;
import org.olat.core.gui.render.RenderingState;
import org.olat.core.gui.render.StringOutput;
import org.olat.core.gui.render.URLBuilder;
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;
/**
* enclosing_type Description: <br>
*
* @author Felix Jost
*/
public class TableRenderer implements ComponentRenderer {
private OLog log = Tracing.createLoggerFor(this.getClass());
protected static final String TABLE_MULTISELECT_GROUP = "tb_ms";
/**
* Constructor for TableRenderer. There must be an empty contructor for the
* Class.forName() call
*/
public TableRenderer() {
super();
}
/**
* @see org.olat.core.gui.render.ui.ComponentRenderer#render(org.olat.core.gui.render.Renderer,
* org.olat.core.gui.render.StringOutput, org.olat.core.gui.components.Component,
* org.olat.core.gui.render.URLBuilder, org.olat.core.gui.translator.Translator,
* org.olat.core.gui.render.RenderResult, java.lang.String[])
*/
public void render(Renderer renderer, StringOutput target, Component source, URLBuilder ubu, Translator translator,
RenderResult renderResult, String[] args) {
Table table = (Table) source;
// render form for multiselect
String formName = "tb_ms_" + source.hashCode();
target.append("<form method=\"post\" name=\"");
target.append(formName);
target.append("\" action=\"");
boolean iframePostEnabled = renderer.getGlobalSettings().getAjaxFlags().isIframePostEnabled();
ubu.buildURI(target, null, null, iframePostEnabled ? AJAXFlags.MODE_TOBGIFRAME : AJAXFlags.MODE_NORMAL);
target.append("\" id=\"");
target.append(formName);
target.append("\"");
if (iframePostEnabled) {
ubu.appendTarget(target);
}
target.append(" onsubmit=\"o_beforeserver();\">");
// starting real table table
target.append("<div class=\"b_overflowscrollbox\" id=\"b_overflowscrollbox_").append(table.hashCode()).append("\"><table id=\"b_table").append(table.hashCode()).append("\">");
int rows = table.getRowCount();
int cols = table.getColumnCount();
boolean asc = table.isSortAscending();
boolean selRowUnSelectable = table.isSelectedRowUnselectable();
// build header links
if (table.isDisplayTableHeader()) {
target.append("<thead><tr>");
ColumnDescriptor sortedCD = table.getCurrentlySortedColumnDescriptor();
for (int i = 0; i < cols; i++) {
ColumnDescriptor cd = table.getColumnDescriptor(i);
String header;
if(cd.translateHeaderKey())
header = translator.translate(cd.getHeaderKey());
else
header = cd.getHeaderKey();
target.append("<th class=\"");
// add css class for first and last column to support older browsers
if (i == 0) target.append(" b_first_child");
if (i == cols-1) target.append(" b_last_child");
target.append("\">");
// add 'move column left' link (if we are not at the leftmost position)
if (i != 0 && table.isColumnMovingOffered()) {
if(table.markMoveL && table.markedColumn == i){
target.append("<span style=\"border:2px solid red;\">");
}
target.append("<a href=\"JavaScript:tableFormInjectCommandAndSubmit('");
target.append(formName).append("', '" + Table.COMMAND_MOVECOLUMN_LEFT + "', '").append(i).append("');\" class=\"b_table_move_left\" title=\"");
target.append(StringEscapeUtils.escapeHtml(translator.translate("row.move.left"))).append("\">«</a> ");
if(table.markMoveL && table.markedColumn == i){
target.append("</span>");
}
}
// header either a link or not
if (table.isSortingEnabled() && cd.isSortingAllowed()) {
if(table.markSort && table.markedColumn == i){
target.append("<span style=\"border:2px solid red;\">");
}
target.append("<a href=\"JavaScript:tableFormInjectCommandAndSubmit('");
target.append(formName).append("', '" + Table.COMMAND_SORTBYCOLUMN + "', '").append(i).append("');\" title=\"");
target.append(StringEscapeUtils.escapeHtml(translator.translate("row.sort"))).append("\">");
target.append(header);
target.append("</a>");
if(table.markSort && table.markedColumn == i){
target.append("</span>");
}
} else {
target.append(header);
}
// mark currently sorted row special
if (table.isSortingEnabled() && cd == sortedCD) {
//REVIEW:fj:pb I think this should look the same as the above link?
if(table.markSort && table.markedColumn == i){
target.append("<span style=\"border:2px solid red;\">");
}
target.append("<a href=\"JavaScript:tableFormInjectCommandAndSubmit('");
target.append(formName).append("', '" + Table.COMMAND_SORTBYCOLUMN + "', '").append(i).append("');\" title=\"");
target.append(StringEscapeUtils.escapeHtml(translator.translate("row.sort.invert"))).append("\"> ");
target.append((asc ? "↓" : "↑"));
target.append("</a>");
if(table.markSort && table.markedColumn == i){
target.append("</span>");
}
}
// add 'move column right' link (if we are not at the rightmost
// position)
if (i != cols - 1 && table.isColumnMovingOffered()) {
if(table.markMoveR && table.markedColumn == i){
target.append("<span style=\"border:2px solid red;\">");
}
target.append("<a href=\"JavaScript:tableFormInjectCommandAndSubmit('");
target.append(formName).append("', '" + Table.COMMAND_MOVECOLUMN_RIGHT + "', '").append(i).append("');\" class=\"b_table_move_right\" title=\"");
target.append(StringEscapeUtils.escapeHtml(translator.translate("row.move.right"))).append("\">»</a>");
if(table.markMoveR && table.markedColumn == i){
target.append("</span>");
}
}
target.append("</th>");
}
target.append("</tr></thead>");
}
// build rows
target.append("<tbody>");
// the really selected rowid (from the tabledatamodel)
int selRowId = table.getSelectedRowId();
String cssClass;
int resultsPerPage = table.getResultsPerPage();
Integer currentPageId = table.getCurrentPageId();
boolean usePageing;
int startRowId = 0;
int endRowId = rows;
// initalize pageing
if (table.isPageingEnabled() && currentPageId != null && !table.isShowAllSelected()) {
startRowId = ((currentPageId.intValue() - 1) * resultsPerPage);
endRowId = startRowId + resultsPerPage;
if (endRowId > rows) endRowId = rows;
usePageing = true;
} else {
startRowId = 0;
endRowId = rows;
usePageing = false;
}
BitSet multiSelectSelectedRows = table.getMultiSelectSelectedRows();
int lastVisibleRowId = endRowId-1;
for (int i = startRowId; i < endRowId; i++) {
// the position of the selected row in the tabledatamodel
int currentPosInModel = table.getSortedRow(i);
boolean isMark = selRowUnSelectable && (selRowId == currentPosInModel);
// use alternating css class
if (i % 2 == 0) cssClass = "";
else cssClass = "b_table_odd";
// add css class for first and last column to support older browsers
if (i == startRowId) cssClass += " b_first_child";
if (i == lastVisibleRowId) cssClass += " b_last_child";
target.append("<tr class=\"").append(cssClass).append("\">");
for (int j = 0; j < cols; j++) {
ColumnDescriptor cd = table.getColumnDescriptor(j);
int alignment = cd.getAlignment();
cssClass = (alignment == ColumnDescriptor.ALIGNMENT_LEFT ? "b_align_normal" : (alignment == ColumnDescriptor.ALIGNMENT_RIGHT ? "b_align_inverse" : "b_align_center"));
// add css class for first and last column to support older browsers
if (j == 0) cssClass += " b_first_child";
if (j == cols-1) cssClass += " b_last_child";
target.append("<td class=\"").append(cssClass);
if (isMark) {
target.append(" b_table_marked");
}
target.append("\">");
if (j == 0) target.append("<a name=\"b_table\"></a>"); //add once for accessabillitykey
String action = cd.getAction(i);
StringOutput so = new StringOutput();
cd.renderValue(so, i, renderer);
String renderval = so.toString();
log.debug("render: i=" + i + " renderval=" + renderval );
if (action != null) {
// If we have actions on the table rows, we just submit traditional style (not via form.submit())
// Note that changes in the state of multiselects will not be reflected in the model.
HrefGenerator hrefG = cd.getHrefGenerator();
if (hrefG != null) {
if(table.markSelectedRowId == currentPosInModel && action.equals(table.markActionId)){
target.append("<span style=\"border:2px solid red;\">");
}
target.append("<a href=\"");
StringOutput link = new StringOutput();
ubu.buildURI(link, new String[] { Table.COMMANDLINK_ROWACTION_CLICKED, Table.COMMANDLINK_ROWACTION_ID }, new String[] {
String.valueOf(currentPosInModel), action }); // url
target.append(hrefG.generate(currentPosInModel, link.toString()));
target.append("\">");
} else if (cd.isPopUpWindowAction()) {
if(table.markSelectedRowId == currentPosInModel && action.equals(table.markActionId)){
target.append("<span style=\"border:2px solid red;\">");
}
// render as popup window
target.append("<a href=\"");
target.append("javascript:{var win=window.open('");
ubu.buildURI(target, new String[] { Table.COMMANDLINK_ROWACTION_CLICKED, Table.COMMANDLINK_ROWACTION_ID }, new String[] {
String.valueOf(currentPosInModel), action }); // url
target.append("','tw_").append(i + "_" + j); // javascript window
// name
target.append("','");
String popUpAttributes = cd.getPopUpWindowAttributes();
if (popUpAttributes != null) target.append(popUpAttributes);
target.append("');win.focus();}\">");
} else {
if(table.markSelectedRowId == currentPosInModel && action.equals(table.markActionId)){
target.append("<span style=\"border:2px solid red;\">");
}
// render in same window
target.append("<a href=\"");
ubu.buildURI(target, new String[] { Table.COMMANDLINK_ROWACTION_CLICKED, Table.COMMANDLINK_ROWACTION_ID }, new String[] {
String.valueOf(currentPosInModel), action }, iframePostEnabled? AJAXFlags.MODE_TOBGIFRAME : AJAXFlags.MODE_NORMAL);
target.append("\" onclick=\"return o2cl()\"");
if (iframePostEnabled) {
ubu.appendTarget(target);
}
target.append(">");
}
target.append(renderval);
target.append("</a>");
if(table.markSelectedRowId == currentPosInModel && action.equals(table.markActionId)){
target.append("</span>");
}
} else {
target.append(renderval);
}
target.append("</td>");
}
target.append("</tr>");
}
// end of table table
target.append("</tbody></table></div>");
// if multiselect, add "select all" / "deselect all" buttons
if (table.isMultiSelect()) {
target.append("<div class=\"b_togglecheck\">");
target.append("<a href=\"#\" onclick=\"javascript:b_table_toggleCheck('" + formName + "', true)\">");
target.append("<input type=\"checkbox\" checked=\"checked\" disabled=\"disabled\" />");
target.append(translator.translate("checkall"));
target.append("</a> <a href=\"#\" onclick=\"javascript:b_table_toggleCheck('" + formName + "', false)\">");
target.append("<input type=\"checkbox\" disabled=\"disabled\" />");
target.append(translator.translate("uncheckall"));
target.append("</a></div>");
}
if(table.isShowAllSelected() && (rows > resultsPerPage) ) {
target.append("<div class=\"b_table_page\">");
target.append("<a href=\"JavaScript:tableFormInjectCommandAndSubmit('");
target.append(formName).append("', '" + Table.COMMAND_PAGEACTION).append("', '" + Table.COMMAND_SHOW_PAGES + "');\" onclick=\"return o2cl();\">");
target.append("[").append(translator.translate("table.showpages")).append("]</a>");
target.append("</div>");
}
// add table paging
if (usePageing && (rows > resultsPerPage)) {
int pageid = currentPageId.intValue();
//paging bug OLAT-935 part missing second page, or missing last page due rounding issues.
int maxpageid = (int)Math.ceil(((double)rows / (double)resultsPerPage));
target.append("<div class=\"b_table_page\">");
// back link if not on first page
if (pageid > 1) {
if(table.markPageBackward){
target.append("<span style=\"border:2px solid red;\">");
}
target.append("<a class=\"b_table_backward\" href=\"JavaScript:tableFormInjectCommandAndSubmit('");
target.append(formName).append("', '" + Table.COMMAND_PAGEACTION + "', '").append(Table.COMMAND_PAGEACTION_BACKWARD).append("');\" onclick=\"return o2cl();\">");
target.append(translator.translate("table.backward")).append("</a>");
if(table.markPageBackward){
target.append("</span>");
}
}
// page number links
if(table.markActivatePageX == pageid){
target.append("<span style=\"border:2px solid red;\">");
}
addPageNumberLinks(target, formName, pageid, maxpageid);
if(table.markActivatePageX == pageid){
target.append("</span>");
}
// next link
if ((pageid * resultsPerPage) < rows) {
if(table.markPageForward){
target.append("<span style=\"border:2px solid red;\">");
}
target.append("<a class=\"b_table_forward\" href=\"JavaScript:tableFormInjectCommandAndSubmit('");
target.append(formName).append("', '" + Table.COMMAND_PAGEACTION + "', '").append(Table.COMMAND_PAGEACTION_FORWARD).append("');\" onclick=\"return o2cl();\">");
target.append(translator.translate("table.forward")).append("</a>");
if(table.markPageForward){
target.append("</span>");
}
}
// show all results link
if (table.isShowAllLinkEnabled()) {
target.append("</div><div class=\"b_table_page_all\">");
if(table.markPageShowAll){
target.append("<span style=\"border:2px solid red;\">");
}
target.append("<a href=\"JavaScript:tableFormInjectCommandAndSubmit('");
target.append(formName).append("', '" + Table.COMMAND_PAGEACTION + "', '").append(Table.COMMAND_PAGEACTION_SHOWALL).append("');\" onclick=\"return o2cl();\">");
target.append("[").append(translator.translate("table.showall")).append("]</a>");
if(table.markPageShowAll){
target.append("</span>");
}
}
target.append("</div>");
}
// add multiselect form actions
List multiSelectActionsi18nKeys = table.getMultiSelectActionsI18nKeys();
List multiSelectActionsIdentifiers = table.getMultiSelectActionsIdentifiers();
if (table.isMultiSelect() && multiSelectActionsi18nKeys.size() == 0) throw new OLATRuntimeException(null,
"Action key in multiselect table is undefined. Use addMultiSelectI18nAction(\"i18nkey\", \"action\"); to set an action for this multiselect table.", null);
target.append("<div class=\"b_table_buttons\">");
for (int i = 0; i < multiSelectActionsi18nKeys.size(); i++) {
String multiSelectActionsi18nKey = (String)multiSelectActionsi18nKeys.get(i);
String multiSelectActionIdentifer = (String)multiSelectActionsIdentifiers.get(i);
if (table.markActionId != null && table.markActionId.equals(multiSelectActionIdentifer))
target.append("<span style=\"border:2px solid red;\">");
target.append("<input type=\"submit\" name=\"" + multiSelectActionIdentifer + "\" value=\""
+ StringEscapeUtils.escapeHtml(translator.translate(multiSelectActionsi18nKey)) + "\" class=\"b_button\" />");
if (table.markActionId != null && table.equals(multiSelectActionIdentifer))
target.append("</span>");
}
target.append("</div>");
// add hidden action command placeholders to the form. these will be manipulated when
// the user clicks on a regular link within the table to e.g. re-sort the columns.
target.append("<input type=\"hidden\" name=\"cmd\" value=\"\" />");
target.append("<input type=\"hidden\" name=\"param\" value=\"\" />");
// close multiselect form
target.append("</form>");
// JS code to resize table to browser view port and display scrollbars in the table
// when not in pageing mode and more than 1000 results are shown.
// This prevents a very strange overflow problem in FF that makes all
// entries after the 1023 entry or even the entire table unreadable.
// Comment CDATA section to make it work with prototype's stripScripts method !
if (!usePageing && rows > 1000) {
target.append("<script type=\"text/javascript\">/* <![CDATA[ */\n ");
target.append("Ext.onReady(function() { $('b_overflowscrollbox_").append(source.hashCode()).append("').setStyle({'height': b_viewportHeight()/3*2 + 'px'});});");
target.append("/* ]]> */\n</script>");
}
}
/**
* @param target
* @param ubu
* @param pageid
* @param maxpageid
*/
private void addPageNumberLinks(StringOutput target, String formName, int pageid, int maxpageid) {
/*
* simple case where only 1 to pageId numbers have to be generated
*/
if (maxpageid < 12) {
for (int i = 1; i <= maxpageid; i++) {
target.append("<a ");
if (pageid == i) {
target.append(" class=\"b_table_page_active\"");
}
target.append(" href=\"JavaScript:tableFormInjectCommandAndSubmit('");
target.append(formName).append("', '" + Table.COMMAND_PAGEACTION + "', '").append(i).append("');\">");
target.append(i).append("</a>");
}
return;
}
int powerOf10 = String.valueOf(maxpageid).length() - 1;
int maxStepSize = (int) Math.pow(10, powerOf10);
int stepSize = (int) Math.pow(10, String.valueOf(pageid).length() - 1);
boolean isStep = false;
int useEveryStep = 3;
int stepCnt = 0;
boolean isNear = false;
int nearleft = 5;
int nearright = 5;
if (pageid < nearleft) {
nearleft = pageid;
nearright += (nearright - nearleft);
} else if (pageid > (maxpageid - nearright)) {
nearright = maxpageid - pageid;
nearleft += (nearleft - nearright);
}
for (int i = 1; i <= maxpageid; i++) {
// adapt stepsize if needed
if (i < pageid && stepSize > 1 && ((pageid - i) / stepSize == 0)) {
stepSize = stepSize / 10;
} else if (i > pageid && stepSize < maxStepSize && ((i - pageid) / stepSize == 9)) {
stepSize = stepSize * 10;
}
isStep = ((i % stepSize) == 0);
if (isStep) {
stepCnt++;
isStep = isStep && (stepCnt % useEveryStep == 0);
}
isNear = (i > (pageid - nearleft) && i < (pageid + nearright));
if (i == 1 || i == maxpageid || isStep || isNear) {
target.append("<a ");
if (pageid == i) {
target.append(" class=\"b_table_page_active\"");
}
target.append(" href=\"JavaScript:tableFormInjectCommandAndSubmit('");
target.append(formName).append("', '" + Table.COMMAND_PAGEACTION + "', '").append(i).append("');\">");
target.append(i).append("</a>");
}
}
}
/**
* @see org.olat.core.gui.render.ui.ComponentRenderer#renderHeaderIncludes(org.olat.core.gui.render.Renderer,
* org.olat.core.gui.render.StringOutput, org.olat.core.gui.components.Component,
* org.olat.core.gui.render.URLBuilder, org.olat.core.gui.translator.Translator)
*/
public void renderHeaderIncludes(Renderer renderer, StringOutput sb, Component source, URLBuilder ubu, Translator translator, RenderingState rstate) {
//
}
/**
* @see org.olat.core.gui.render.ui.ComponentRenderer#renderBodyOnLoadJSFunctionCall(org.olat.core.gui.render.Renderer,
* org.olat.core.gui.render.StringOutput, org.olat.core.gui.components.Component)
*/
public void renderBodyOnLoadJSFunctionCall(Renderer renderer, StringOutput sb, Component source, RenderingState rstate) {
//
}
}