/** * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * <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 the * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> * <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> * Initial code contributed and copyrighted by<br> * frentix GmbH, http://www.frentix.com * <p> */ package org.olat.core.gui.components.form.flexible.impl.elements.table; import java.util.Arrays; import java.util.List; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.DefaultComponentRenderer; import org.olat.core.gui.components.form.flexible.FormItem; import org.olat.core.gui.components.form.flexible.elements.FlexiTableFilter; import org.olat.core.gui.components.form.flexible.elements.FlexiTableSort; import org.olat.core.gui.components.form.flexible.impl.Form; import org.olat.core.gui.components.form.flexible.impl.FormJSHelper; import org.olat.core.gui.components.form.flexible.impl.NameValuePair; import org.olat.core.gui.render.RenderResult; import org.olat.core.gui.render.Renderer; 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.util.StringHelper; /** * * Initial date: 01.03.2013<br> * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ public abstract class AbstractFlexiTableRenderer extends DefaultComponentRenderer { @Override public void render(Renderer renderer, StringOutput sb, Component source, URLBuilder ubu, Translator translator, RenderResult renderResult, String[] args) { FlexiTableComponent ftC = (FlexiTableComponent) source; FlexiTableElementImpl ftE = ftC.getFlexiTableElement(); String id = ftC.getFormDispatchId(); renderHeaderButtons(renderer, sb, ftE, ubu, translator, renderResult, args); if(ftE.getTableDataModel().getRowCount() == 0 && StringHelper.containsNonWhitespace(ftE.getEmtpyTableMessageKey())) { String emptyMessageKey = ftE.getEmtpyTableMessageKey(); sb.append("<div class='o_info'>") .append(translator.translate(emptyMessageKey)) .append("</div>"); } else { sb.append("<div class='o_table_wrapper o_table_flexi") .append(" o_table_edit", ftE.isEditMode()); String css = ftE.getElementCssClass(); if (css != null) { sb.append(" ").append(css); } switch(ftE.getRendererType()) { case custom: sb.append(" o_rendertype_custom"); break; case classic: sb.append(" o_rendertype_classic"); break; } sb.append("'"); String wrapperSelector = ftE.getWrapperSelector(); if (wrapperSelector != null) { sb.append(" id='").append(wrapperSelector).append("'"); } sb.append("><table id=\"").append(id).append("\" class=\"table table-condensed table-striped table-hover") .append(" table-bordered", ftE.isBordered()).append("\">"); //render headers renderHeaders(sb, ftC, translator); //render body sb.append("<tbody>"); renderBody(renderer, sb, ftC, ubu, translator, renderResult); sb.append("</tbody></table>"); renderFooterButtons(sb, ftC, translator); //draggable if(ftE.getColumnIndexForDragAndDropLabel() > 0) { sb.append("<script type='text/javascript'>") .append("/* <![CDATA[ */ \n") .append("jQuery(function() {\n") .append(" jQuery('.o_table_flexi table tr').draggable({\n") .append(" containment: '#o_main',\n") .append(" zIndex: 10000,\n") .append(" cursorAt: {left: 0, top: 0},\n") .append(" accept: function(event,ui){ return true; },\n") .append(" helper: function(event,ui,zt) {\n") .append(" var helperText = jQuery(this).children('.o_dnd_label').text();\n") .append(" return jQuery(\"<div class='ui-widget-header o_table_drag'>\" + helperText + \"</div>\").appendTo('body').css('zIndex',5).show();\n") .append(" }\n") .append("});\n") .append("});\n") .append("/* ]]> */\n") .append("</script>\n"); } sb.append("</div>"); } //source if (source.isEnabled()) { FormJSHelper.appendFlexiFormDirty(sb, ftE.getRootForm(), id); } } protected void renderHeaderButtons(Renderer renderer, StringOutput sb, FlexiTableElementImpl ftE, URLBuilder ubu, Translator translator, RenderResult renderResult, String[] args) { Component searchCmp = ftE.getExtendedSearchComponent(); if(searchCmp == null && !ftE.isExtendedSearchExpanded() && !ftE.isNumOfRowsEnabled() && !ftE.isFilterEnabled() && !ftE.isSortEnabled() && ! ftE.isExportEnabled() && !ftE.isCustomizeColumns() && ftE.getAvailableRendererTypes().length <= 1) { return; } if(searchCmp != null && ftE.isExtendedSearchExpanded()) { renderer.render(searchCmp, sb, args); } sb.append("<div class='row clearfix o_table_toolbar'>") .append("<div class='col-sm-6 col-xs-12 o_table_toolbar_left'>"); if(searchCmp == null || !ftE.isExtendedSearchExpanded()) { renderHeaderSearch(renderer, sb, ftE, ubu, translator, renderResult, args); } sb.append("</div>"); sb.append("<div class='col-sm-2 col-xs-4 o_table_row_count'>"); if(ftE.isNumOfRowsEnabled()) { int rowCount = ftE.getTableDataModel().getRowCount(); if(rowCount == 1) { sb.append(rowCount).append(" ").append(ftE.getTranslator().translate("table.entry")); } else if(rowCount > 1) { sb.append(rowCount).append(" ").append(ftE.getTranslator().translate("table.entries")); } } sb.append("</div><div class='col-sm-4 col-xs-8'><div class='pull-right'><div class='o_table_tools o_noprint'>"); boolean empty = ftE.getTableDataModel().getRowCount() == 0; String filterIndication = null; //filter if(ftE.isFilterEnabled()) { List<FlexiTableFilter> filters = ftE.getFilters(); if(filters != null && filters.size() > 0) { filterIndication = renderFilterDropdown(sb, ftE, filters); } } //sort if(ftE.isSortEnabled()) { List<FlexiTableSort> sorts = ftE.getSorts(); if(sorts != null && sorts.size() > 0) { renderSortDropdown(sb, ftE, sorts); } } if(ftE.getExportButton() != null && ftE.isExportEnabled()) { sb.append("<div class='btn-group'>"); ftE.getExportButton().setEnabled(!empty); renderFormItem(renderer, sb, ftE.getExportButton(), ubu, translator, renderResult, args); sb.append("</div> "); } if(ftE.getCustomButton() != null && ftE.isCustomizeColumns() && (ftE.getRendererType() == null || ftE.getRendererType() == FlexiTableRendererType.classic)) { sb.append("<div class='btn-group'>"); renderFormItem(renderer, sb, ftE.getCustomButton(), ubu, translator, renderResult, args); sb.append("</div> "); } //switch type of tables FlexiTableRendererType[] types = ftE.getAvailableRendererTypes(); if(types.length > 1) { sb.append("<div class='btn-group'>"); for(FlexiTableRendererType type:types) { renderHeaderSwitchType(type, renderer, sb, ftE, ubu, translator, renderResult, args); } sb.append("</div> "); } sb.append("</div>"); if(StringHelper.containsNonWhitespace(filterIndication)) { Form theForm = ftE.getRootForm(); String dispatchId = ftE.getFormDispatchId(); sb.append("<div class='o_table_tools_indications'>").append(filterIndication) // remove filter .append(" <a href=\"javascript:") .append(FormJSHelper.getXHRFnCallFor(theForm, dispatchId, 1, true, true, true, new NameValuePair("rm-filter", "true"))) .append("\" title=\"").append(translator.translate("remove.filters")).append("\" ") .append("\">").append("<i class='o_icon o_icon_remove o_icon-fw'> </i> </a></li></div>"); } sb.append("</div>"); sb.append("</div></div>"); } protected void renderHeaderSearch(Renderer renderer, StringOutput sb, FlexiTableElementImpl ftE, URLBuilder ubu, Translator translator, RenderResult renderResult, String[] args) { if(ftE.isSearchEnabled()) { Form theForm = ftE.getRootForm(); String dispatchId = ftE.getFormDispatchId(); sb.append("<div class='o_table_search input-group o_noprint'>"); renderFormItem(renderer, sb, ftE.getSearchElement(), ubu, translator, renderResult, args); sb.append("<div class='input-group-btn'>"); // reset quick search String id = ftE.getSearchElement().getFormDispatchId(); sb.append("<a href=\"javascript:jQuery('#").append(id).append("').val('');") .append(FormJSHelper.getXHRFnCallFor(theForm, dispatchId, 1, true, true, true, new NameValuePair("reset-search", "true"))) .append("\" class='btn o_reset_quick_search'><i class='o_icon o_icon_remove_filters'> </i></a>"); renderFormItem(renderer, sb, ftE.getSearchButton(), ubu, translator, renderResult, args); if(ftE.getExtendedSearchButton() != null) { renderFormItem(renderer, sb, ftE.getExtendedSearchButton(), ubu, translator, renderResult, args); } StringBuilder filterIndication = new StringBuilder(); if(ftE.getExtendedFilterButton() != null) { ftE.getSelectedExtendedFilters().forEach(filter -> { if(filterIndication.length() > 0) filterIndication.append(", "); filterIndication.append(filter.getLabel()); }); renderFormItem(renderer, sb, ftE.getExtendedFilterButton(), ubu, translator, renderResult, args); } sb.append("</div>"); sb.append("</div>"); if(filterIndication.length() > 0) { sb.append("<div class='o_table_tools_indications'>").append(filterIndication) // remove filter .append("<a href=\"javascript:") .append(FormJSHelper.getXHRFnCallFor(theForm, dispatchId, 1, true, true, true, new NameValuePair("rm-extended-filter", "true"))) .append("\" title=\"").append(translator.translate("remove.filters")).append("\" ") .append("\">").append("<i class='o_icon o_icon_remove o_icon-fw'> </i> </a></li></div>"); } } else if(ftE.getExtendedSearchButton() != null) { renderFormItem(renderer, sb, ftE.getExtendedSearchButton(), ubu, translator, renderResult, args); } } protected String renderFilterDropdown(StringOutput sb, FlexiTableElementImpl ftE, List<FlexiTableFilter> filters) { Form theForm = ftE.getRootForm(); String dispatchId = ftE.getFormDispatchId(); String selected = null; sb.append("<div class='btn-group'>") .append("<button id='table-button-filters-").append(dispatchId).append("' type='button' class='btn btn-default dropdown-toggle' data-toggle='dropdown'>") .append("<i class='o_icon o_icon_filter o_icon-lg'> </i> <b class='caret'></b></button>") .append("<div id='table-filters-").append(dispatchId).append("' class='hide'><ul class='o_dropdown list-unstyled' role='menu'>"); for(FlexiTableFilter filter:filters) { if(FlexiTableFilter.SPACER.equals(filter)) { sb.append("<li class='divider'></li>"); } else { sb.append("<li><a href=\"javascript:") .append(FormJSHelper.getXHRFnCallFor(theForm, dispatchId, 1, true, true, true, new NameValuePair("filter", filter.getFilter()))) .append("\">").append("<i class='o_icon o_icon_check o_icon-fw'> </i> ", filter.isSelected()); if(filter.getIconLeftCSS() != null) { sb.append("<i class='o_icon ").append(filter.getIconLeftCSS()).append("'> </i> "); } if(filter.getIconRenderer() != null) { filter.getIconRenderer().render(sb, filter, ftE.getComponent(), ftE.getTranslator()); } sb.append(filter.getLabel()).append("</a></li>"); if(filter.isSelected() && !filter.isShowAll()) { selected = filter.getLabel(); } } } sb.append("</ul></div></div> ") .append("<script type='text/javascript'>\n") .append("/* <![CDATA[ */\n") .append("jQuery(function() { o_popover('table-button-filters-").append(dispatchId).append("','table-filters-").append(dispatchId).append("'); });\n") .append("/* ]]> */\n") .append("</script>"); return selected; } protected void renderSortDropdown(StringOutput sb, FlexiTableElementImpl ftE, List<FlexiTableSort> sorts) { Form theForm = ftE.getRootForm(); String dispatchId = ftE.getFormDispatchId(); sb.append("<div class='btn-group'>") .append("<button id='table-button-sorters-").append(dispatchId).append("' type='button' class='btn btn-default dropdown-toggle' data-toggle='dropdown'>") .append("<i class='o_icon o_icon_sort_menu o_icon-lg'> </i> <b class='caret'></b></button>") .append("<div id='table-sorters-").append(dispatchId).append("' class='hide'><ul class='o_dropdown list-unstyled' role='menu'>"); for(FlexiTableSort sort:sorts) { if(FlexiTableSort.SPACER.equals(sort)) { sb.append("<li class='divider'></li>"); } else { sb.append("<li><a href=\"javascript:") .append(FormJSHelper.getXHRFnCallFor(theForm, dispatchId, 1, true, true, true, new NameValuePair("sort", sort.getSortKey().getKey()), new NameValuePair("asc", sort.getSortKey().isAsc() ? "desc" : "asc"))) .append("\">"); if(sort.isSelected()) { if(sort.getSortKey().isAsc()) { sb.append("<i class='o_icon o_icon_sort_desc o_icon-fw'> </i> "); } else { sb.append("<i class='o_icon o_icon_sort_asc o_icon-fw'> </i> "); } } sb.append(sort.getLabel()).append("</a></li>"); } } sb.append("</ul></div></div> ") .append("<script type='text/javascript'>\n") .append("/* <![CDATA[ */\n") .append("jQuery(function() { o_popover('table-button-sorters-").append(dispatchId).append("','table-sorters-").append(dispatchId).append("'); });\n") .append("/* ]]> */\n") .append("</script>"); } protected void renderHeaderSwitchType(FlexiTableRendererType type, Renderer renderer, StringOutput sb, FlexiTableElementImpl ftE, URLBuilder ubu, Translator translator, RenderResult renderResult, String[] args) { if(type != null) { switch(type) { case custom: { renderFormItem(renderer, sb, ftE.getCustomTypeButton(), ubu, translator, renderResult, args); break; } case classic: { renderFormItem(renderer, sb, ftE.getClassicTypeButton(), ubu, translator, renderResult, args); break; } } } } protected void renderFormItem(Renderer renderer, StringOutput sb, FormItem item, URLBuilder ubu, Translator translator, RenderResult renderResult, String[] args) { if(item != null) { Component cmp = item.getComponent(); cmp.getHTMLRendererSingleton().render(renderer, sb, cmp, ubu, translator, renderResult, args); } } protected void renderFooterButtons(StringOutput sb, FlexiTableComponent ftC, Translator translator) { FlexiTableElementImpl ftE = ftC.getFlexiTableElement(); if(ftE.isSelectAllEnable()) { String formName = ftE.getRootForm().getFormName(); String dispatchId = ftE.getFormDispatchId(); sb.append("<div class='o_table_footer'><div class='o_table_checkall input-sm'>"); sb.append("<label class='checkbox-inline'><a id='") .append(dispatchId).append("_sa' href=\"javascript:o_table_toggleCheck('").append(formName).append("', true);") .append(FormJSHelper.getXHRFnCallFor(ftE.getRootForm(), dispatchId, 1, true, true, true, new NameValuePair("select", "checkall"))) .append("\"><i class='o_icon o_icon-lg o_icon_check_on'> </i> <span>").append(translator.translate("form.checkall")) .append("</span></a></label>"); sb.append("<label class='checkbox-inline'><a id='") .append(dispatchId).append("_dsa' href=\"javascript:o_table_toggleCheck('").append(formName).append("', false);") .append(FormJSHelper.getXHRFnCallFor(ftE.getRootForm(), dispatchId, 1, true, true, true, new NameValuePair("select", "uncheckall"))) .append("\"><i class='o_icon o_icon-lg o_icon_check_off'> </i> <span>").append(translator.translate("form.uncheckall")) .append("</span></a></label>"); sb.append("</div></div>"); } if(ftE.getDefaultPageSize() > 0) { renderPagesLinks(sb, ftC, translator); } } protected abstract void renderHeaders(StringOutput target, FlexiTableComponent ftC, Translator translator); protected void renderBody(Renderer renderer, StringOutput target, FlexiTableComponent ftC, URLBuilder ubu, Translator translator, RenderResult renderResult) { String id = ftC.getFormDispatchId(); FlexiTableElementImpl ftE = ftC.getFlexiTableElement(); FlexiTableDataModel<?> dataModel = ftE.getTableDataModel(); // the really selected rowid (from the tabledatamodel) int firstRow = ftE.getFirstRow(); int maxRows = ftE.getMaxRows(); int rows = dataModel.getRowCount(); int lastRow = Math.min(rows, firstRow + maxRows); String rowIdPrefix = "row_" + id + "-"; for (int i = firstRow; i < lastRow; i++) { if(dataModel.isRowLoaded(i)) { renderRow(renderer, target, ftC, rowIdPrefix, i, ubu, translator, renderResult); } } // end of table table } protected abstract void renderRow(Renderer renderer, StringOutput target, FlexiTableComponent ftC, String rowIdPrefix, int row, URLBuilder ubu, Translator translator, RenderResult renderResult); private void renderPagesLinks(StringOutput sb, FlexiTableComponent ftC, Translator translator) { FlexiTableElementImpl ftE = ftC.getFlexiTableElement(); int pageSize = ftE.getPageSize(); FlexiTableDataModel<?> dataModel = ftE.getTableDataModel(); int rows = dataModel.getRowCount(); if (rows > ftE.getDefaultPageSize()) { renderPageSize(sb, ftC, translator); } sb.append("<ul class='pagination'>"); if(pageSize > 0 && rows > pageSize) { int page = ftE.getPage(); int maxPage = (int)Math.ceil(((double) rows / (double) pageSize)); renderPageBackLink(sb, ftC, page); renderPageNumberLinks(sb, ftC, page, maxPage); renderPageNextLink(sb, ftC, page, maxPage); } sb.append("</ul>"); } private void renderPageSize(StringOutput sb, FlexiTableComponent ftC, Translator translator) { FlexiTableElementImpl ftE = ftC.getFlexiTableElement(); FlexiTableDataModel<?> dataModel = ftE.getTableDataModel(); Form theForm = ftE.getRootForm(); String dispatchId = ftE.getFormDispatchId(); int pageSize = ftE.getPageSize(); int firstRow = ftE.getFirstRow(); int maxRows = ftE.getMaxRows(); int rows = dataModel.getRowCount(); int lastRow = Math.min(rows, firstRow + maxRows); sb.append("<div class='o_table_rows_infos o_noprint'>"); sb.append(translator.translate("page.size.a", new String[] { Integer.toString(firstRow + 1),//for humans Integer.toString(lastRow), Integer.toString(rows) })) .append(" "); sb.append("<div class='btn-group dropup'><button type='button' class='btn btn-default dropdown-toggle' data-toggle='dropdown' aria-expanded='false'>") .append(" <span>"); if(pageSize < 0) { sb.append(translator.translate("show.all")); } else { sb.append(Integer.toString(pageSize)); } sb.append("</span> <span class='caret'></span></button>") .append("<ul class='dropdown-menu' role='menu'>"); int[] sizes = new int[]{ 20, 50, 100, 250 }; int defaultPageSize = ftE.getDefaultPageSize(); if (Arrays.binarySearch(sizes, defaultPageSize) < 0) { sizes = new int[]{ 20, 50, 100, 250, defaultPageSize }; Arrays.sort(sizes); } for(int size:sizes) { sb.append("<li><a href=\"javascript:") .append(FormJSHelper.getXHRFnCallFor(theForm, dispatchId, 1, true, true, true, new NameValuePair("pagesize", Integer.toString(size)))) .append("\">").append(Integer.toString(size)).append("</a></li>"); } if(ftE.isShowAllRowsEnabled()) { sb.append("<li><a href=\"javascript:") .append(FormJSHelper.getXHRFnCallFor(theForm, dispatchId, 1, true, true, true, new NameValuePair("pagesize", "all"))) .append("\">").append(translator.translate("show.all")).append("</a></li>"); } sb.append("</ul></div>") .append(" ").append(translator.translate("page.size.b")) .append("</div> "); } private void renderPageBackLink(StringOutput sb, FlexiTableComponent ftC, int page) { boolean disabled = (page <= 0); FlexiTableElementImpl ftE = ftC.getFlexiTableElement(); Form theForm = ftE.getRootForm(); sb.append("<li").append(" class='disabled'", disabled).append("><a href=\""); if(disabled) { sb.append("#"); } else { sb.append("javascript:") .append(FormJSHelper.getXHRFnCallFor(theForm, ftC.getFormDispatchId(), 1, true, true, true, new NameValuePair("page", Integer.toString(page - 1)))); } sb.append("\">").append("«").append("</a></li>"); } private void renderPageNextLink(StringOutput sb, FlexiTableComponent ftC, int page, int maxPage) { boolean disabled = (page >= maxPage); FlexiTableElementImpl ftE = ftC.getFlexiTableElement(); Form theForm = ftE.getRootForm(); sb.append("<li ").append(" class='disabled'", disabled).append("><a href=\""); if(disabled) { sb.append("#"); } else { sb.append("javascript:") .append(FormJSHelper.getXHRFnCallFor(theForm, ftC.getFormDispatchId(), 1, true, true, true, new NameValuePair("page", Integer.toString(page + 1)))); } sb.append("\">").append("»").append("</a></li>"); } private void renderPageNumberLinks(StringOutput sb, FlexiTableComponent ftC, int page, int maxPage) { if (maxPage < 12) { for (int i=0; i<maxPage; i++) { appendPagenNumberLink(sb, ftC, page, i); } } else { int powerOf10 = String.valueOf(maxPage).length() - 1; int maxStepSize = (int) Math.pow(10, powerOf10); int stepSize = (int) Math.pow(10, String.valueOf(page).length() - 1); boolean isStep = false; int useEveryStep = 3; int stepCnt = 0; boolean isNear = false; int nearleft = 5; int nearright = 5; if (page < nearleft) { nearleft = page; nearright += (nearright - nearleft); } else if (page > (maxPage - nearright)) { nearright = maxPage - page; nearleft += (nearleft - nearright); } for (int i = 0; i <= maxPage; i++) { // adapt stepsize if needed stepSize = adaptStepIfNeeded(page, maxStepSize, stepSize, i); isStep = ((i % stepSize) == 0); if (isStep) { stepCnt++; isStep = isStep && (stepCnt % useEveryStep == 0); } isNear = (i > (page - nearleft) && i < (page + nearright)); if (i == 0 || i == maxPage || isStep || isNear) { appendPagenNumberLink(sb, ftC, page, i); } } } } private void appendPagenNumberLink(StringOutput sb, FlexiTableComponent ftC, int page, int i) { FlexiTableElementImpl ftE = ftC.getFlexiTableElement(); Form theForm = ftE.getRootForm(); sb.append("<li").append(" class='active'", (page == i)).append("><a href=\"javascript:") .append(FormJSHelper.getXHRFnCallFor(theForm, ftC.getFormDispatchId(), 1, true, true, true, new NameValuePair("page", Integer.toString(i)))) .append(";\">").append(i+1).append("</a></li>"); } private int adaptStepIfNeeded(final int page, final int maxStepSize, final int stepSize, final int i) { int newStepSize = stepSize; if (i < page && stepSize > 1 && ((page - i) / stepSize == 0)) { newStepSize = stepSize / 10; } else if (i > page && stepSize < maxStepSize && ((i - page) / stepSize == 9)) { newStepSize = stepSize * 10; } return newStepSize; } }