/*******************************************************************************
* Copyright (c) 2007-2008 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributor:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.jboss.tools.jsf.vpe.richfaces.template;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.jboss.tools.jsf.vpe.richfaces.ComponentUtil;
import org.jboss.tools.jsf.vpe.richfaces.RichFacesTemplatesActivator;
import org.jboss.tools.vpe.editor.context.VpePageContext;
import org.jboss.tools.vpe.editor.template.VpeChildrenInfo;
import org.jboss.tools.vpe.editor.template.VpeCreationData;
import org.jboss.tools.vpe.editor.template.VpeCreatorUtil;
import org.jboss.tools.vpe.editor.template.VpeTemplate;
import org.jboss.tools.vpe.editor.util.HTML;
import org.jboss.tools.vpe.editor.util.SourceDomUtil;
import org.jboss.tools.vpe.editor.util.VisualDomUtil;
import org.mozilla.interfaces.nsIDOMDocument;
import org.mozilla.interfaces.nsIDOMElement;
import org.mozilla.interfaces.nsIDOMNode;
import org.mozilla.interfaces.nsIDOMNodeList;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* The class {@code RichFacesDataTableChildrenEncoder} encodes children of a {@code rich:*Table}.
*
* <BR/>Use this class as follows:
* <blockquote><pre>
* RichFacesDataTableChildrenEncoder encoder
* = new RichFacesDataTableChildrenEncoder(
* creationData, visualDocument, sourceElement, table);
* encoder.encodeChildren();</pre></blockquote>
*
* Method {@link #validateChildren(VpePageContext, Node, nsIDOMDocument, VpeCreationData) validateChildren}
* MUST be invoked from {@link VpeTemplate#validate(VpePageContext, Node, nsIDOMDocument, VpeCreationData) validate}
* method of the caller of this class:
* <blockquote><pre>
* public void validate(VpePageContext pageContext, Node sourceNode,
* nsIDOMDocument visualDocument, VpeCreationData data) {
* RichFacesDataTableChildrenEncoder.validateChildren(
* pageContext, sourceNode, visualDocument, data);
* ...
* }</pre></blockquote>
*
* @author yradtsevich
* */
class RichFacesDataTableChildrenEncoder {
private String firstRowClass = "dr-table-firstrow rich-table-firstrow"; //$NON-NLS-1$
private String nonFirstRowClass = "dr-table-row rich-table-row"; //$NON-NLS-1$
/**@param firstRowClass the class of the first row in the table
* @param nonFirstRowClass the class of all rows in the table except the first one*/
public void setRowClasses(final String firstRowClass, final String nonFirstRowClass) {
this.firstRowClass = firstRowClass;
this.nonFirstRowClass = nonFirstRowClass;
}
/**Non-HTML tag that is used to create temporary containers for {@code rich:subTable} and {@code rich:columnGroup}.*/
private static final String TAG_SUB_TABLE_OR_COLUMN_GROUP_CONTAINER = "subTableOrColumnGroup-container"; //$NON-NLS-1$
private final VpeCreationData creationData;
private final nsIDOMDocument visualDocument;
private final Element tableSourceElement;
private final nsIDOMElement tableVisualTag;
public RichFacesDataTableChildrenEncoder(final VpeCreationData creationData,
final nsIDOMDocument visualDocument, final Element tableSourceElement,
final nsIDOMElement tableVisualTag) {
this.creationData = creationData;
this.visualDocument = visualDocument;
this.tableSourceElement = tableSourceElement;
this.tableVisualTag = tableVisualTag;
}
/**
* Creates containers in {@code table} for {@code sourceElement}'s children
* and adds appropriate objects of {@link VpeChildrenInfo} to {@code creationData}.
*
* <BR/>It knows about following tags:
* {@code rich:column, rich:columns, rich:subTable} and {@code rich:columnGroup}.
* <BR/>For any another tag it uses {@link #addElementToTable(Node)} method.
* */
public void encodeChildren() {
// create an empty childrenInfo. It tells to VpeVisualDomBuilder
// that it is not necessary to add any child of the sourceElement
// except ones specified in another vpeChildrenInfo's
final VpeChildrenInfo childInfo = new VpeChildrenInfo(null);
creationData.addChildrenInfo(childInfo);
final List<Node> children = ComponentUtil.getChildren(tableSourceElement);
boolean createNewRow = true;
for (final Node child : children) {
final String nodeName = child.getNodeName();
if (nodeName.endsWith(RichFaces.TAG_COLUMN) ||
nodeName.endsWith(RichFaces.TAG_COLUMNS)) {
createNewRow |= RichFacesColumnTemplate.isBreakBefore(child);
addColumnToRow(child, createNewRow);
createNewRow = false;
} else if(nodeName.endsWith(RichFaces.TAG_SUB_TABLE)
|| nodeName.endsWith(RichFaces.TAG_COLUMN_GROUP)) {
addSubTableOrColumnGroupToTable(child);
createNewRow = true;
} else if (!VpeCreatorUtil.isFacet(child)) {
addElementToTable(child);
createNewRow = true;
}
}
}
/**
* Makes necessary changes in the table's body after all children of the table have been encoded.
*/
public static void validateChildren(final VpePageContext pageContext, final Node sourceNode,
final nsIDOMDocument visualDocument, final VpeCreationData creationData) {
final nsIDOMNode visualNode = creationData.getNode();
fixSubTables(visualNode);
}
/**
* Creates a container for {@code subTableOrColumnGroupNode} in {@code table}
* and adds an appropriate object of {@link VpeChildrenInfo} to {@code creationData}.
* <BR/>The container is the tag {@link #TAG_SUB_TABLE_OR_COLUMN_GROUP_CONTAINER}.
*/
private nsIDOMElement addSubTableOrColumnGroupToTable(final Node subTableOrColumnGroupNode) {
final nsIDOMElement subTableOrColumnGroupContainer = visualDocument
.createElement(TAG_SUB_TABLE_OR_COLUMN_GROUP_CONTAINER);
tableVisualTag.appendChild(subTableOrColumnGroupContainer);
final VpeChildrenInfo childInfo = new VpeChildrenInfo(subTableOrColumnGroupContainer);
childInfo.addSourceChild(subTableOrColumnGroupNode);
creationData.addChildrenInfo(childInfo);
return subTableOrColumnGroupContainer;
}
private nsIDOMElement currentRow = null;
private VpeChildrenInfo currentRowChildrenInfo = null;
private int rowNumber = 0;
/**
* Creates a container for {@code columnNode} in {@code table}
* and adds an appropriate object of {@link VpeChildrenInfo} to {@code creationData}.
* <BR/>If the parameter {@code createNewRow} is {@code true} then it creates the
* container in a new row.
* */
private nsIDOMElement addColumnToRow(final Node columnNode, final boolean createNewRow) {
if ( createNewRow || (currentRow == null) ) {
currentRow = visualDocument.createElement(HTML.TAG_TR);
tableVisualTag.appendChild(currentRow);
currentRowChildrenInfo = new VpeChildrenInfo(currentRow);
creationData.addChildrenInfo(currentRowChildrenInfo);
rowNumber++;
if (rowNumber == 1) {
currentRow.setAttribute(HTML.ATTR_CLASS, firstRowClass);
} else {
currentRow.setAttribute(HTML.ATTR_CLASS, nonFirstRowClass);
}
}
currentRowChildrenInfo.addSourceChild(columnNode);
return currentRow;
}
/**
* Creates a row container for {@code node} in {@code table}
* and adds an appropriate object of {@link VpeChildrenInfo} to {@code creationData}.
* <BR/>The container spans the entire row.
* */
private void addElementToTable(final Node node) {
final nsIDOMElement tr = this.visualDocument.createElement(HTML.TAG_TR);
tableVisualTag.appendChild(tr);
final nsIDOMElement td = this.visualDocument.createElement(HTML.TAG_TD);
td.setAttribute(HTML.ATTR_COLSPAN, HTML.VALUE_COLSPAN_ALL);
tr.appendChild(td);
final VpeChildrenInfo childInfo = new VpeChildrenInfo(td);
childInfo.addSourceChild(node);
creationData.addChildrenInfo(childInfo);
}
/**
* Replaces all occurencies of {@link #TAG_SUB_TABLE_OR_COLUMN_GROUP_CONTAINER} tag in
* the {@code visualNode} by the tag's child.
* @see #addSubTableOrColumnGroupToTable(Node)
*/
private static void fixSubTables(final nsIDOMNode visualNode) {
final nsIDOMElement element = (nsIDOMElement) visualNode;
final nsIDOMNodeList subTableContainers = element.getElementsByTagName(TAG_SUB_TABLE_OR_COLUMN_GROUP_CONTAINER);
final long length = subTableContainers.getLength();
for (int i = 0; i < length; i++) {
final nsIDOMNode subTableContainer = subTableContainers.item(0);
final nsIDOMNodeList subTableContainerChildren = subTableContainer.getChildNodes();
if (subTableContainerChildren == null
|| subTableContainerChildren.getLength() != 1) {
final RuntimeException e = new RuntimeException("This is probably a bug. subTable-container should have one inner tag.");//$NON-NLS-1$
RichFacesTemplatesActivator.getPluginLog().logError(e);
}
VisualDomUtil.replaceNodeByItsChildren(subTableContainer);
}
}
/**
* Encoded facets from columns to the table.
*
* @param pageContext VpePageContext
* @param columnsWithFacets list of the source elements for columns
* @param facetName {@code "header"} or {@code "footer"} or other
* @param visualParentTR visal element to put {@code "<tr>"} to
* @param visualElementForTD string name for facet cell (usually {@code <td>})
* @param cellClass css class for the facet's cell
* @param headerClass user defined css class for column's header
*/
public void encodeColumnsFacets(VpePageContext pageContext,
ArrayList<Element> columnsWithFacets, String facetName,
nsIDOMElement visualParentTR, String visualElementForTD,
String cellClass, String headerClass) {
for (Element column : columnsWithFacets) {
Element facet = SourceDomUtil.getFacetByName(pageContext,column, facetName);
/*
* If facet is null unwanted cells might be added.
* Thus do not add TD for such facets.
*/
if (null != facet) {
String classAttribute = facetName + "Class"; //$NON-NLS-1$
String columnHeaderClass = column.hasAttribute(classAttribute) ? column.getAttribute(classAttribute) : null;
nsIDOMElement td = visualDocument.createElement(visualElementForTD);
visualParentTR.appendChild(td);
String styleClass = ComponentUtil.encodeStyleClass(null, cellClass, headerClass, columnHeaderClass);
if (!RichFacesColumnTemplate.isVisible(column)) {
VisualDomUtil.setSubAttribute(td, HTML.ATTR_STYLE,
HTML.STYLE_PARAMETER_DISPLAY, HTML.STYLE_VALUE_NONE);
}
td.setAttribute(HTML.ATTR_CLASS, styleClass);
td.setAttribute(HTML.ATTR_SCOPE, HTML.TAG_COL);
if(column.hasAttribute(HTML.ATTR_COLSPAN)) {
String colspan = column.getAttribute(HTML.ATTR_COLSPAN);
td.setAttribute(HTML.ATTR_COLSPAN, colspan);
}
if (RichFaces.NAME_FACET_HEADER.equals(facetName)) {
nsIDOMElement icon = RichFacesColumnTemplate.getHeaderIcon(pageContext, column, visualDocument);
if (icon != null) {
td.appendChild(icon);
}
}
/*
* Add facet source here
*/
VpeChildrenInfo childrenInfo = new VpeChildrenInfo(td);
childrenInfo.addSourceChild(facet);
creationData.addChildrenInfo(childrenInfo);
}
}
}
/**
* Adds the header or footer facets to the table
*
* @param pageContext VpePageContext
* @param visualTagForFacet {@code <thead>} or {@code <tfoot>} or any similar
* @param innerFacetSourceNode single source elemen``t to be rendered inside facet
* @param visualElementForTD string name for facet cell (usually {@code <td>})
* @param facetTHeadClass css class for {@code <thead>} or {@code <tfoot>} or any similar
* @param firstRowClass css class for the first facet row
* @param rowClass css class for the facet's row
* @param cellClass css class for the facet's cell
*/
public void encodeTableFacets(VpePageContext pageContext,
nsIDOMElement visualTagForFacet, Element innerFacetSourceNode,
String visualElementForTD, String facetTHeadClass,
String firstRowClass, String rowClass, String cellClass) {
if (null == innerFacetSourceNode) {
RichFacesTemplatesActivator.getDefault().logError("Source element to be rendered inside facet is 'null' !"); //$NON-NLS-1$
}
boolean isColumnGroup = innerFacetSourceNode.getNodeName().endsWith(RichFaces.TAG_COLUMN_GROUP);
boolean isSubTable = innerFacetSourceNode.getNodeName().endsWith(RichFaces.TAG_SUB_TABLE);
if(isColumnGroup) {
RichFacesColumnGroupTemplate.DEFAULT_INSTANCE.encodeSubTable(pageContext, creationData, innerFacetSourceNode, visualDocument, visualTagForFacet);
} else if(isSubTable) {
RichFacesSubTableTemplate.DEFAULT_INSTANCE.encodeSubTable(pageContext, creationData, innerFacetSourceNode, visualDocument, visualTagForFacet);
} else {
nsIDOMElement tr = visualDocument.createElement(HTML.TAG_TR);
visualTagForFacet.appendChild(tr);
// TODO facetTHeadClass should be applied only once
// TODO rowClass is never applied
String styleClass = ComponentUtil.encodeStyleClass(null, firstRowClass, facetTHeadClass, null);
if(styleClass!=null) {
tr.setAttribute(HTML.ATTR_CLASS, styleClass);
}
String style = ComponentUtil.getHeaderBackgoundImgStyle();
tr.setAttribute(HTML.ATTR_STYLE, style);
nsIDOMElement td = visualDocument.createElement(visualElementForTD);
tr.appendChild(td);
// TODO facetTHeadClass should be applied only once
styleClass = ComponentUtil.encodeStyleClass(null, cellClass, facetTHeadClass, null);
if(styleClass!=null) {
td.setAttribute(HTML.ATTR_CLASS, styleClass);
}
// the cell spans the entire row
td.setAttribute(HTML.ATTR_COLSPAN, HTML.VALUE_COLSPAN_ALL);
td.setAttribute(HTML.ATTR_SCOPE, HTML.TAG_COLGROUP);
VpeChildrenInfo child = new VpeChildrenInfo(td);
child.addSourceChild(innerFacetSourceNode);
creationData.addChildrenInfo(child);
}
}
/**
* Encodes the whole table's header and columns' header
*
* @param pageContext VpePageContext
* @param visualParentForFacetTHead visual node to put facet's {@code <thead>} or {@code <tfoot>} or similar
* @param visualTagForFacetTHead what tag will be rendered: {@code <thead>} or {@code <tfoot>} or other
* @param visualElementForTD string name for facet cell (usually {@code <td>})
* @param facetName {@code "header"} or {@code "footer"} or other
* @param customFacetClass user's class for columns' headers/footer
* @param facetTHeadClass user's class for table's headers/footer
* @param firstRowClass css class for the first row in table's headers/footer
* @param rowClass css class for non-first row in table's headers/footer
* @param cellClass css class for the facet's cell
*/
public void encodeTableHeader(VpePageContext pageContext,
nsIDOMElement visualParentForFacetTHead,
String visualTagForFacetTHead, String visualElementForTD,
String facetName, String customFacetClass, String facetTHeadClass,
String firstRowClass, String rowClass, String cellClass) {
Element facetSourceElement = SourceDomUtil.getFacetByName(pageContext,
tableSourceElement, facetName);
ArrayList<Element> columns = RichFaces.getColumns(tableSourceElement);
int columnsLength = RichFaces.getColumnsCount(tableSourceElement, columns);
Map<String, List<Node>> facetChildren = VisualDomUtil.findFacetElements(facetSourceElement, pageContext);
boolean headerJsfElementPresents = facetChildren.get(VisualDomUtil.FACET_JSF_TAG).size() > 0;
boolean hasColumnWithFacets = RichFaces.hasColumnWithFacet(columns, facetName);
if(headerJsfElementPresents || hasColumnWithFacets) {
nsIDOMElement createdVisualTagForFacetTHead = null;
if ((null == visualTagForFacetTHead) || "".equalsIgnoreCase(visualTagForFacetTHead)) { //$NON-NLS-1$
/*
* For subtables mostly:
* Header won't be placed into separate thead or tfoot tag.
* Then put it to the parent's visual node.
*/
createdVisualTagForFacetTHead = visualParentForFacetTHead;
} else {
createdVisualTagForFacetTHead = visualDocument.createElement(
visualTagForFacetTHead); //thead or tfoot
visualParentForFacetTHead.appendChild(createdVisualTagForFacetTHead);
}
String facetClass = null;
if (tableSourceElement.hasAttribute(facetName + "Class")) { //$NON-NLS-1$
facetClass = tableSourceElement.getAttribute(facetName + "Class"); //$NON-NLS-1$
}
/*
* Encode facet for the whole table
*/
if(headerJsfElementPresents) {
Element node = (Element) facetChildren.get(VisualDomUtil.FACET_JSF_TAG).get(0);
encodeTableFacets(pageContext,
createdVisualTagForFacetTHead, node, visualElementForTD,
facetTHeadClass, firstRowClass, rowClass, cellClass);
}
/*
* Encode facets for columns
*/
if(hasColumnWithFacets) {
nsIDOMElement visualParentTR = visualDocument.createElement(HTML.TAG_TR);
createdVisualTagForFacetTHead.appendChild(visualParentTR);
String styleClass = ComponentUtil.encodeStyleClass(null, customFacetClass, null, facetClass);
if(styleClass!=null) {
visualParentTR.setAttribute(HTML.ATTR_CLASS, styleClass);
}
encodeColumnsFacets(pageContext,
columns, facetName, visualParentTR, visualElementForTD,
cellClass, facetClass);
}
}
}
/**
* Encodes the whole table's footer and columns' footer
*
* @param pageContext VpePageContext
* @param visualParentForFacetTHead visual node to put facet's {@code <thead>} or {@code <tfoot>} or similar
* @param visualTagForFacetTHead what tag will be rendered: {@code <thead>} or {@code <tfoot>} or other
* @param visualElementForTD string name for facet cell (usually {@code <td>})
* @param facetName {@code "header"} or {@code "footer"} or other
* @param customFacetClass user's class for columns' headers/footer
* @param facetTHeadClass user's class for table's headers/footer
* @param firstRowClass css class for the first row in table's headers/footer
* @param rowClass css class for non-first row in table's headers/footer
* @param cellClass css class for the facet's cell
*/
public void encodeTableFooter(VpePageContext pageContext,
nsIDOMElement visualParentForFacetTHead,
String visualTagForFacetTHead, String visualElementForTD,
String facetName, String customFacetClass, String facetTHeadClass,
String firstRowClass, String rowClass, String cellClass) {
Element facetSourceElement = SourceDomUtil.getFacetByName(pageContext,
tableSourceElement, facetName);
ArrayList<Element> columns = RichFaces.getColumns(tableSourceElement);
int columnsLength = RichFaces.getColumnsCount(tableSourceElement, columns);
Map<String, List<Node>> facetChildren = VisualDomUtil.findFacetElements(facetSourceElement, pageContext);
boolean headerJsfElementPresents = facetChildren.get(VisualDomUtil.FACET_JSF_TAG).size() > 0;
boolean hasColumnWithFacets = RichFaces.hasColumnWithFacet(columns, facetName);
if(headerJsfElementPresents || hasColumnWithFacets) {
nsIDOMElement createdVisualTagForFacetTHead = null;
if ((null == visualTagForFacetTHead) || "".equalsIgnoreCase(visualTagForFacetTHead)) { //$NON-NLS-1$
/*
* For subtables mostly:
* Header won't be placed into separate thead or tfoot tag.
* Then put it to the parent's visual node.
*/
createdVisualTagForFacetTHead = visualParentForFacetTHead;
} else {
createdVisualTagForFacetTHead = visualDocument.createElement(
visualTagForFacetTHead); //thead or tfoot
visualParentForFacetTHead.appendChild(createdVisualTagForFacetTHead);
}
String facetClass = null;
if (tableSourceElement.hasAttribute(facetName + "Class")) { //$NON-NLS-1$
facetClass = tableSourceElement.getAttribute(facetName + "Class"); //$NON-NLS-1$
}
/*
* Encode facets for columns first
*/
if(hasColumnWithFacets) {
nsIDOMElement visualParentTR = visualDocument.createElement(HTML.TAG_TR);
createdVisualTagForFacetTHead.appendChild(visualParentTR);
String styleClass = ComponentUtil.encodeStyleClass(null, customFacetClass, null, facetClass);
if(styleClass!=null) {
visualParentTR.setAttribute(HTML.ATTR_CLASS, styleClass);
}
encodeColumnsFacets(pageContext,
columns, facetName, visualParentTR, visualElementForTD,
cellClass, facetClass);
}
/*
* Encode facet for the whole table
*/
if(headerJsfElementPresents) {
Element node = (Element) facetChildren.get(VisualDomUtil.FACET_JSF_TAG).get(0);
encodeTableFacets(pageContext,
createdVisualTagForFacetTHead, node, visualElementForTD,
facetTHeadClass, firstRowClass, rowClass, cellClass);
}
}
}
}