使用 JSF Datatable 有条件地显示行
Posted
技术标签:
【中文标题】使用 JSF Datatable 有条件地显示行【英文标题】:Conditionally display row using JSF Datatable 【发布时间】:2011-02-19 05:50:39 【问题描述】:我有一些当前可以工作的 JSF 代码(如下所示),我需要对其进行修改以有条件地禁止显示表格的某些行。我知道如何有条件地抑制特定单元格的显示,但这似乎会创建一个空单元格,而我想要做的是根本不显示该行。
有什么建议吗?
<h:dataTable styleClass="resultsTable" id="t1" value="#r.common" var="com" headerClass="headerBackgrnd" rowClasses="rowOdd, rowEven" columnClasses="leftAlign, rightAlign, leftAlign">
<h:column>
<h:outputText rendered="#com.rendered" styleClass="inputText" value="#com.description: " />
</h:column>
<h:column>
<h:outputText styleClass="outputText" value="#com.v1" />
</h:column>
<h:column>
<h:inputText styleClass="inputText" value="#com.v2" />
</h:column>
</h:dataTable>
基本上,#com.rendered
的行将有条件地显示单个单元格的内容,当 com.rendered
为 false 时会生成一个空单元格。但我想在某些情况下跳过整行显示 - 我该怎么做?
【问题讨论】:
【参考方案1】:通过在所有<h:column>
标签中放置一个渲染属性,我成功地隐藏了行。问题是它抑制了表格标题。如果您的表格没有表格标题(它们是嵌入在<h:column>
中的<f:facet name="header">
标签),这种方法可能适合您。
我最终在支持 bean 中使用了多个列表,因为我需要表头。
【讨论】:
试过这个,除非我做错了所有列都呈现解析为FALSE
的行,仍然呈现为空行(即<tr></tr>
)【参考方案2】:
使用此处建议的空 css 选择器,但使用 tr 而不是 td。这对我有用。
https://***.com/a/19177424
如此处所述:https://developer.mozilla.org/en-US/docs/Web/CSS/:empty 此选择器适用于当前所有浏览器。
<style>
tr:empty
display: none;
</style>
【讨论】:
【参考方案3】:我扩展 HtmlTableRenderer 默认渲染器并覆盖 renderRowStart 方法,通过将 style 属性赋予 table- >tr 元素,值为 display:none。
绑定列表中的item需要实现TableRow接口,只有一个isHide方法。在具体类中,您可以放置您喜欢的任何逻辑来提供布尔值。
顺便说一句,在这个自定义渲染器中也有类似 PrimeFaces 实现的函数,它在表为空时给出消息,并且 table->tr 将自动计算表中有多少列并为 colspan 属性提供适当的值。
public class MyDataTableRenderer extends htmlTableRenderer
private static final Integer[] ZERO_INT_ARRAY = new Integer[] 0 ;
private static final String NO_RESULT_MESSAGE_ATTR_NAME = "noResultMessage";
private static final String defaultEmptyMessage = "No records found";
private static final Logger log = Logger.getLogger(DHSDataTableRenderer.class.getName());
@Override
public void encodeInnerHtml(FacesContext facesContext, UIComponent component) throws IOException
UIData uiData = (UIData) component;
String message = (String) uiData.getAttributes().get(NO_RESULT_MESSAGE_ATTR_NAME);
if (message == null || "".equals(message.trim()))
message = defaultEmptyMessage;
ResponseWriter writer = facesContext.getResponseWriter();
int rowCount = uiData.getRowCount();
int newspaperColumns = getNewspaperColumns(component);
int columnNumber = getChildCount(component);
if (rowCount == -1 && newspaperColumns == 1)
encodeInnerHtmlUnknownRowCount(facesContext, component);
return;
if (rowCount == 0)
// nothing to render, to get valid xhtml we render an empty dummy
// row
writer.startElement(HTML.TBODY_ELEM, uiData);
writer.writeAttribute(HTML.ID_ATTR, component.getClientId(facesContext) + ":tbody_element", null);
writer.startElement(HTML.TR_ELEM, uiData);
writer.startElement(HTML.TD_ELEM, uiData);
writer.writeAttribute(HTML.COLSPAN_ATTR, columnNumber, null);
writer.writeAttribute(HTML.CLASS_ATTR, "dhs-empty-table", null);
writer.write(message);
writer.endElement(HTML.TD_ELEM);
writer.endElement(HTML.TR_ELEM);
writer.endElement(HTML.TBODY_ELEM);
return;
// begin the table
// get the CSS styles
Styles styles = getStyles(uiData);
int first = uiData.getFirst();
int rows = uiData.getRows();
int last;
if (rows <= 0)
last = rowCount;
else
last = first + rows;
if (last > rowCount)
last = rowCount;
int newspaperRows;
if ((last - first) % newspaperColumns == 0)
newspaperRows = (last - first) / newspaperColumns;
else
newspaperRows = ((last - first) / newspaperColumns) + 1;
boolean newspaperHorizontalOrientation = isNewspaperHorizontalOrientation(component);
// get the row indizes for which a new TBODY element should be created
Integer[] bodyrows = getBodyRows(facesContext, component);
int bodyrowsCount = 0;
// walk through the newspaper rows
for (int nr = 0; nr < newspaperRows; nr++)
boolean rowStartRendered = false;
// walk through the newspaper columns
for (int nc = 0; nc < newspaperColumns; nc++)
// the current row in the 'real' table
int currentRow;
if (newspaperHorizontalOrientation)
currentRow = nr * newspaperColumns + nc + first;
else
currentRow = nc * newspaperRows + nr + first;
// if this row is not to be rendered
if (currentRow >= last)
continue;
// bail if any row does not exist
uiData.setRowIndex(currentRow);
if (!uiData.isRowAvailable())
log.severe("Row is not available. Rowindex = " + currentRow);
break;
if (nc == 0)
// first column in table, start new row
beforeRow(facesContext, uiData);
// is the current row listed in the bodyrows attribute
if (ArrayUtils.contains(bodyrows, currentRow))
// close any preopened TBODY element first
if (bodyrowsCount != 0)
HtmlRendererUtils.writePrettyLineSeparator(facesContext);
writer.endElement(HTML.TBODY_ELEM);
HtmlRendererUtils.writePrettyLineSeparator(facesContext);
writer.startElement(HTML.TBODY_ELEM, uiData);
// Do not attach bodyrowsCount to the first TBODY
// element, because of backward compatibility
writer.writeAttribute(HTML.ID_ATTR, component.getClientId(facesContext) + ":tbody_element" + (bodyrowsCount == 0 ? "" : bodyrowsCount),
null);
bodyrowsCount++;
HtmlRendererUtils.writePrettyLineSeparator(facesContext);
renderRowStart(facesContext, writer, uiData, styles, nr);
rowStartRendered = true;
List<UIComponent> children = null;
for (int j = 0, size = getChildCount(component); j < size; j++)
if (children == null)
children = getChildren(component);
UIComponent child = children.get(j);
if (child.isRendered())
boolean columnRendering = child instanceof UIColumn;
if (columnRendering)
beforeColumn(facesContext, uiData, j);
encodeColumnChild(facesContext, writer, uiData, child, styles, nc * uiData.getChildCount() + j);
if (columnRendering)
afterColumn(facesContext, uiData, j);
if (hasNewspaperTableSpacer(uiData))
// draw the spacer facet
if (nc < newspaperColumns - 1)
renderSpacerCell(facesContext, writer, uiData);
if (rowStartRendered)
renderRowEnd(facesContext, writer, uiData);
afterRow(facesContext, uiData);
if (bodyrowsCount != 0)
// close the last TBODY element
HtmlRendererUtils.writePrettyLineSeparator(facesContext);
writer.endElement(HTML.TBODY_ELEM);
@Override
protected void renderRowStart(FacesContext facesContext, ResponseWriter writer, UIData uiData, Styles styles, int rowStyleIndex) throws IOException
writer.startElement(HTML.TR_ELEM, null); // uiData);
renderRowStyle(facesContext, writer, uiData, styles, rowStyleIndex);
Object obj = uiData.getRowData();
boolean isHide = false;
if (obj instanceof TableRow)
isHide = ((TableRow) obj).isHide();
if (isHide)
writer.writeAttribute("style", "display: none;", null);
Object rowId = uiData.getAttributes().get(org.apache.myfaces.shared.renderkit.JSFAttr.ROW_ID);
if (rowId != null)
writer.writeAttribute(HTML.ID_ATTR, rowId.toString(), null);
private void encodeInnerHtmlUnknownRowCount(FacesContext facesContext, UIComponent component) throws IOException
UIData uiData = (UIData) component;
ResponseWriter writer = facesContext.getResponseWriter();
Styles styles = getStyles(uiData);
Integer[] bodyrows = getBodyRows(facesContext, component);
int bodyrowsCount = 0;
int first = uiData.getFirst();
int rows = uiData.getRows();
int currentRow = first;
boolean isRowRendered = false;
while (true)
uiData.setRowIndex(currentRow);
if (!uiData.isRowAvailable())
break;
isRowRendered = true;
// first column in table, start new row
beforeRow(facesContext, uiData);
// is the current row listed in the bodyrows attribute
if (ArrayUtils.contains(bodyrows, currentRow))
// close any preopened TBODY element first
if (bodyrowsCount != 0)
HtmlRendererUtils.writePrettyLineSeparator(facesContext);
writer.endElement(HTML.TBODY_ELEM);
HtmlRendererUtils.writePrettyLineSeparator(facesContext);
writer.startElement(HTML.TBODY_ELEM, uiData);
// Do not attach bodyrowsCount to the first TBODY element,
// because of backward compatibility
writer.writeAttribute(HTML.ID_ATTR, component.getClientId(facesContext) + ":tbody_element" + (bodyrowsCount == 0 ? "" : bodyrowsCount), null);
bodyrowsCount++;
HtmlRendererUtils.writePrettyLineSeparator(facesContext);
renderRowStart(facesContext, writer, uiData, styles, currentRow);
List<UIComponent> children = null;
for (int j = 0, size = getChildCount(component); j < size; j++)
if (children == null)
children = getChildren(component);
UIComponent child = children.get(j);
if (child.isRendered())
boolean columnRendering = child instanceof UIColumn;
if (columnRendering)
beforeColumn(facesContext, uiData, j);
encodeColumnChild(facesContext, writer, uiData, child, styles, j);
if (columnRendering)
afterColumn(facesContext, uiData, j);
renderRowEnd(facesContext, writer, uiData);
afterRow(facesContext, uiData);
currentRow++;
if (rows > 0 && currentRow - first > rows)
break;
if (!isRowRendered)
// nothing to render, to get valid xhtml we render an empty dummy
// row
writer.startElement(HTML.TBODY_ELEM, uiData);
writer.writeAttribute(HTML.ID_ATTR, component.getClientId(facesContext) + ":tbody_element", null);
writer.startElement(HTML.TR_ELEM, uiData);
writer.startElement(HTML.TD_ELEM, uiData);
writer.endElement(HTML.TD_ELEM);
writer.endElement(HTML.TR_ELEM);
writer.endElement(HTML.TBODY_ELEM);
return;
if (bodyrowsCount != 0)
// close the last TBODY element
HtmlRendererUtils.writePrettyLineSeparator(facesContext);
writer.endElement(HTML.TBODY_ELEM);
private Integer[] getBodyRows(FacesContext facesContext, UIComponent component)
Integer[] bodyrows = null;
String bodyrowsAttr = (String) component.getAttributes().get(JSFAttr.BODYROWS_ATTR);
if (bodyrowsAttr != null && !"".equals(bodyrowsAttr))
String[] bodyrowsString = StringUtils.trim(StringUtils.splitShortString(bodyrowsAttr, ','));
// parsing with no exception handling, because of JSF-spec:
// "If present, this must be a comma separated list of integers."
bodyrows = new Integer[bodyrowsString.length];
for (int i = 0; i < bodyrowsString.length; i++)
bodyrows[i] = new Integer(bodyrowsString[i]);
else
bodyrows = ZERO_INT_ARRAY;
return bodyrows;
【讨论】:
在我之前的cmets中忘记提及了。我们需要将以下新元素添加到 faces-config.xml<render-kit> <renderer> <component-family>javax.faces.Data</component-family> <renderer-type>javax.faces.Table</renderer-type> <renderer-class>xxx.xxx.MyDataTableRenderer </renderer-class> </renderer> </render-kit>
@Laila Agaev【参考方案4】:
Brian 解决方案的扩展。 为了显示列名,我在 primefaces 中做了以下操作
<p:dataTable value="#eiBean.dce.ilDbConns" var="c">
<p:columnGroup type="header">
<p:row>
<p:column colspan="1" />
<p:column colspan="1" />
</p:row>
<p:row>
<p:column headerText="DataBase Type" />
<p:column headerText="URL" />
</p:row>
</p:columnGroup>
<p:column rendered='#c.conType == "TARGET"'>
<p:outputLabel value="#c.dbType" />
</p:column>
<p:column rendered='#c.conType == "TARGET"'>
<p:outputLabel value="#c.dbUrl" />
</p:column>
</p:dataTable>
【讨论】:
【参考方案5】:对于使用richFaces 的人,您可以使用rich:column 的filterExpression 属性。
<rich:column filterExpression="#put your expression here">
...
</rich>
如果不满足条件,则过滤掉整行。
示例是使用接缝 EL!
【讨论】:
我不明白这是如何工作的,您对列过滤器表达式的设置但行消失了吗?【参考方案6】:行对应于表集合中的数据对象。如果您不想要该行,请不要将对象放入集合中。
或者,您可以将rowClasses
参数用于dataTable。
豆码:
public String getRowClasses()
StringBuilder sb = new StringBuilder();
for (Data data : myData)
sb.append(data.hide ? 'hide,' : 'show,');
return sb.toString();
CSS:
tr.hide display:none;
【讨论】:
抱歉,这不是一个真正的选项 - 此表显示某些应用程序范围的参考数据,除非在此特定情况下,数据只能在某些条件下显示。 那么两个集合,一个包含所有数据,第二个包含用于显示的内容? 问题是我有 2 行,根据情况,有时会显示 A 行,有时会显示 B 行,有时会同时显示两者。保留多套意味着我必须保留三个版本,这并不理想。我希望有另一种方法可以做到这一点。 它仍然只有 2 个集合,您只需根据需要更改显示集合的内容。但是,如果您不介意在您的 M 中添加一个小 V,则可以通过单个集合以编程方式执行此操作。上面已编辑。 纳加纳尔夫是对的。在 bean 级别过滤它。遍历主要的List
并仅收集另一个List
中感兴趣的条目。它不应该那么贵,因为 List
只包含引用。以上是关于使用 JSF Datatable 有条件地显示行的主要内容,如果未能解决你的问题,请参考以下文章
如何在没有 JSF 标记库(h:datatable 或 ui:repeat)的情况下编写 <table> 标记,但仍使用 JSF 来控制页面流