/******************************************************************************* * 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); } } } }