/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/msgcntr/trunk/messageforums-app/src/java/org/sakaiproject/tool/messageforums/jsf/HierDataTableRender.java $ * $Id: HierDataTableRender.java 9227 2006-05-15 15:02:42Z cwen@iupui.edu $ *********************************************************************************** * * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008 The Sakai Foundation * * Licensed under the Educational Community License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.opensource.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * **********************************************************************************/ package org.sakaiproject.tool.messageforums.jsf; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.faces.component.UIColumn; import javax.faces.component.UIComponent; import javax.faces.component.UIData; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; import javax.faces.el.ValueBinding; import org.sakaiproject.api.app.messageforums.Message; import org.sakaiproject.tool.messageforums.ui.DiscussionMessageBean; import com.sun.faces.renderkit.html_basic.HtmlBasicRenderer; import com.sun.faces.util.Util; /** * @author cwen * * TODO To change the template for this generated type comment go to * Window - Preferences - Java - Code Style - Code Templates */ public class HierDataTableRender extends HtmlBasicRenderer { protected static Log log = LogFactory.getLog(HierDataTableRender.class); private static final String RESOURCE_PATH = "/messageforums-tool"; private static final String BARIMG = RESOURCE_PATH + "/" + "images/collapse.gif"; private static final String EXPIMG = RESOURCE_PATH + "/" + "images/expand.gif"; private static final String CURSOR = "cursor:pointer"; private class RenderData { public List<UIColumn> uiColumns = new ArrayList<UIColumn>(); public int facetCountHeaders = 0; public int facetCountFooters = 0; } public boolean getRendersChildren() { return true; } public void encodeBegin(FacesContext context, UIComponent component) throws IOException { if ((context == null) || (component == null)) { throw new NullPointerException(Util.getExceptionMessageString(Util.NULL_PARAMETERS_ERROR_MESSAGE_ID)); } if (log.isTraceEnabled()) { log.trace("Begin encoding component " + component.getId()); } // suppress rendering if "rendered" property on the component is false. if (!component.isRendered()) { if (log.isTraceEnabled()) { log.trace("No encoding necessary " + component.getId() + " since " + "rendered attribute is set to false "); } return; } // prepare the data for processing UIData data = (UIData) component; RenderData theData = organizeTheKids(data); data.setRowIndex(-1); // Render the beginning of the table ResponseWriter writer = context.getResponseWriter(); writer.startElement("table", data); writeIdAttributeIfNecessary(context, writer, component); String noArrows = (String) data.getAttributes().get("noarrows"); if (noArrows == null) { noArrows = ""; } String styleClass = (String) data.getAttributes().get("styleClass"); if (styleClass != null) { writer.writeAttribute("class", styleClass, "styleClass"); } Util.renderPassThruAttributes(writer, component, new String[] { "rows" }); writer.writeText("\n", null); // Render the header facets (if any) UIComponent header = getFacet(data, "header"); String headerClass = (String) data.getAttributes().get("headerClass"); if ((header != null) || (theData.facetCountHeaders > 0)) { writer.startElement("thead", data); writer.writeText("\n", null); } if (header != null) { writer.startElement("tr", header); writer.startElement("th", header); if (headerClass != null) { writer.writeAttribute("class", headerClass, "headerClass"); } writer.writeAttribute("colspan", theData.uiColumns.size(), null); writer.writeAttribute("scope", "colgroup", null); encodeRecursive(context, header); writer.endElement("th"); writer.endElement("tr"); writer.writeText("\n", null); } if (theData.facetCountHeaders > 0) { writer.startElement("tr", data); writer.writeText("\n", null); Iterator columns = theData.uiColumns.iterator(); UIColumn oldColumn = null; while (columns.hasNext()) { UIColumn column = (UIColumn) columns.next(); // write column for arrows... only if last column did not specify arrows if (column.getId().endsWith("_msg_subject") && !oldColumn.getId().endsWith("_toggle") && !"true".equals(noArrows)) { writer.startElement("th", null); writer.writeAttribute("scope", "col", null); writer.endElement("th"); writer.writeText("\n", null); } writer.startElement("th", column); if (headerClass != null) { writer.writeAttribute("class", headerClass, "headerClass"); } writer.writeAttribute("scope", "col", null); UIComponent facet = getFacet(column, "header"); if (facet != null) { encodeRecursive(context, facet); } writer.endElement("th"); writer.writeText("\n", null); oldColumn = column; } writer.endElement("tr"); writer.writeText("\n", null); } if ((header != null) || (theData.facetCountHeaders > 0)) { writer.endElement("thead"); writer.writeText("\n", null); } // Render the footer facets (if any) UIComponent footer = getFacet(data, "footer"); String footerClass = (String) data.getAttributes().get("footerClass"); if ((footer != null) || (theData.facetCountFooters > 0)) { writer.startElement("tfoot", data); writer.writeText("\n", null); } if (footer != null) { writer.startElement("tr", footer); writer.startElement("td", footer); if (footerClass != null) { writer.writeAttribute("class", footerClass, "footerClass"); } writer.writeAttribute("colspan", theData.uiColumns.size(), null); encodeRecursive(context, footer); writer.endElement("td"); writer.endElement("tr"); writer.writeText("\n", null); } if (theData.facetCountFooters > 0) { writer.startElement("tr", data); writer.writeText("\n", null); Iterator columns = theData.uiColumns.iterator(); while (columns.hasNext()) { UIColumn column = (UIColumn) columns.next(); writer.startElement("td", column); if (footerClass != null) { writer.writeAttribute("class", footerClass, "footerClass"); } UIComponent facet = getFacet(column, "footer"); if (facet != null) { encodeRecursive(context, facet); } writer.endElement("td"); writer.writeText("\n", null); } writer.endElement("tr"); writer.writeText("\n", null); } if ((footer != null) || (theData.facetCountFooters > 0)) { writer.endElement("tfoot"); writer.writeText("\n", null); } } public void encodeChildren(FacesContext context, UIComponent component) throws IOException { if ((context == null) || (component == null)) { throw new NullPointerException(Util.getExceptionMessageString(Util.NULL_PARAMETERS_ERROR_MESSAGE_ID)); } if (log.isTraceEnabled()) { log.trace("Begin encoding children " + component.getId()); } if (!component.isRendered()) { if (log.isTraceEnabled()) { log.trace("No encoding necessary " + component.getId() + " since " + "rendered attribute is set to false "); } return; } UIData data = (UIData) component; RenderData theData = organizeTheKids(data); String columnClasses[] = getColumnClasses(data); String rowClasses[] = getRowClasses(data); Map<Long, List<Long>> msgChildren = new HashMap<Long, List<Long>>(); ResponseWriter writer = context.getResponseWriter(); int currRowClass = 0; boolean noArrows = "true".equals(data.getAttributes().get("noarrows")); ValueBinding expandedBinding = component.getValueBinding("expanded"); boolean expanded = (expandedBinding != null && "true".equalsIgnoreCase((String) expandedBinding.getValue(context))); // these variables will be used to track progress in the loops Message currentThread = null; boolean displayToggle = false; boolean checkExpanded = false; writer.startElement("tbody", component); writer.writeText("\n", null); for (data.setRowIndex(data.getFirst()); data.isRowAvailable(); data.setRowIndex(data.getRowIndex() + 1)) { DiscussionMessageBean dmb = (DiscussionMessageBean) data.getRowData(); // // if this row has been deleted... skip it! // if (dmb.getDeleted()) { // continue; // } // walk up the messages to get the parent "thread" Message tmpMsg = dmb.getMessage(); while (tmpMsg.getInReplyTo() != null) { tmpMsg = tmpMsg.getInReplyTo(); } checkExpanded = false; writer.startElement("tr", data); boolean display_moveCheckbox = false; // if this row should be hidden initially, setup those styles/classes if (currentThread == null || !tmpMsg.getId().equals(currentThread.getId())) { writer.writeAttribute("class", "hierItemBlock", null); currentThread = tmpMsg; displayToggle = !noArrows && dmb.getChildCount() > 0; display_moveCheckbox = true; dmb.setDepth(0); } else if (!noArrows) { writer.writeAttribute("style", "display: none", null); writer.writeAttribute("id", "_id_" + dmb.getMessage().getId() + "__hide_division_", null); checkExpanded = true; display_moveCheckbox = false; } if (!noArrows && dmb.getMessage().getInReplyTo() != null) { List<Long> tmpMsgChildren = msgChildren.get(tmpMsg.getId()); if (tmpMsgChildren != null) tmpMsgChildren.add(dmb.getMessage().getId()); } if (rowClasses.length > 0) { writer.writeAttribute("class", rowClasses[currRowClass++], "rowClasses"); if (currRowClass >= rowClasses.length) currRowClass = 0; } writer.writeText("\n", null); // now process each of the columns int currColumnClass = 0; Iterator columns = theData.uiColumns.iterator(); while (columns.hasNext()) { UIColumn column = (UIColumn) columns.next(); writer.startElement("td", null); if (columnClasses.length > 0) { writer.writeAttribute("class", columnClasses[currColumnClass++], "columnClasses"); if (currColumnClass >= columnClasses.length) currColumnClass = 0; } // if hierItemBlock if ((display_moveCheckbox) && (column.getId().endsWith("_checkbox")) && (dmb.getRevise()) && (!dmb.getDeleted())) { writer.startElement("input", null); writer.writeAttribute("id", "moveCheckbox", null); writer.writeAttribute("type", "checkbox", null); writer.writeAttribute("name", "moveCheckbox", null); writer.writeAttribute("onclick", "enableDisableMoveThreadLink();", null); writer.writeAttribute("value", dmb.getMessage().getId(), null); writer.endElement("input"); writer.endElement("td"); continue; } else { if (column.getId().endsWith("_checkbox")) { writer.endElement("td"); continue; } } if (displayToggle) { // write the toggle td if necessary if (dmb.getChildCount() > 0) { writer.startElement("img", null); writer.writeAttribute("src", BARIMG, null); writer.writeAttribute("style", CURSOR, null); writer.writeAttribute("id", "_id_" + dmb.getMessage().getId() + "__img_hide_division_", null); writer.writeAttribute("onclick", "displayChildren('" + dmb.getMessage().getId() + "'); " + "mySetMainFrameHeight('Main" + org.sakaiproject.tool.cover.ToolManager.getCurrentPlacement().getId().replace("-", "x") + "');" + "if (msgExpanded['" + dmb.getMessage().getId() + "']) { this.src='" + BARIMG + "'; } else { this.src='" + EXPIMG + "'; }" + "msgExpanded['" + dmb.getMessage().getId() + "'] = !msgExpanded['" + dmb.getMessage().getId() + "'];", null); msgChildren.put(dmb.getMessage().getId(), new ArrayList<Long>()); } displayToggle = false; if (column.getId().endsWith("_toggle")) { writer.endElement("td"); continue; } } if (column.getId().endsWith("_msg_subject")) { writer.writeAttribute("style", "padding-left: " + dmb.getDepth() + "em;", "style"); } // Render the contents of this cell by iterating over the kids of our kids encodeRecursive(context, column); writer.endElement("td"); writer.writeText("\n", null); } writer.endElement("tr"); writer.writeText("\n", null); if (checkExpanded && expanded) { writer.write( "<script type=\"text/javascript\">\n" + " showHideDiv('_id_" + dmb.getMessage().getId() + "', '" + RESOURCE_PATH + "');\n" + "</script>\n"); } } writer.endElement("tbody"); writer.writeText("\n", null); // write the javascript that will allow the threads to be expanded and minimized if (!noArrows) { StringBuilder javascript = new StringBuilder(); javascript .append("<script type=\"text/javascript\">\n") .append(" var msgChildren = new Array();\n") .append(" var msgExpanded = new Array();\n"); for (Entry<Long, List<Long>> entry: msgChildren.entrySet()) { if (entry.getValue().size() > 0) { javascript.append(" msgChildren['").append(entry.getKey()).append("'] = new Array("); for (Long id: entry.getValue()) { javascript.append("'_id_").append(id).append("',"); } javascript.setLength(javascript.length() - 1); // remove that last extra comma javascript .append(");\n") .append(" msgExpanded['").append(entry.getKey()).append("'] = false;\n"); } } javascript .append("\n function displayChildren(msgId) {\n") .append(" for (var i=0; i < msgChildren[msgId].length; i++) {\n") .append(" showHideDiv(msgChildren[msgId][i], '").append(RESOURCE_PATH).append("');\n") .append(" }\n") .append(" }\n") .append("</script>"); writer.write(javascript.toString()); } // Clean up after ourselves data.setRowIndex(-1); if (log.isTraceEnabled()) { log.trace("End encoding children " + component.getId()); } } public void encodeEnd(FacesContext context, UIComponent component) throws IOException { if ((context == null) || (component == null)) { throw new NullPointerException(Util.getExceptionMessageString(Util.NULL_PARAMETERS_ERROR_MESSAGE_ID)); } if (!component.isRendered()) { if (log.isTraceEnabled()) { log.trace("No encoding necessary " + component.getId() + " since " + "rendered attribute is set to false "); } return; } UIData data = (UIData) component; data.setRowIndex(-1); ResponseWriter writer = context.getResponseWriter(); // Render the ending of this table writer.endElement("table"); writer.writeText("\n", null); if (log.isTraceEnabled()) { log.trace("End encoding component " + component.getId()); } } /** * Processes the child {@link UIColumn} components that are nested in the data parameter and returns the data gleaned from the process as an object of RenderData. * * @param data * The component being rendered. * @return Returns an instance of RenderData whose three components contain the following data: * <ul> * <li>uiColumns - After this function is executed, will contain only the rendered UIColumn components in data</li> * <li>facetCountHeaders - After this function is executed, will contain only the number of "header" facets in data</li> * <li>facetCountFooters - After this fucntion is executed, will contain only the number of "footer" facets in data</li> * </ul> */ private RenderData organizeTheKids(UIData data) { RenderData returnVal = new RenderData(); returnVal.uiColumns = new ArrayList<UIColumn>(); returnVal.facetCountHeaders = 0; returnVal.facetCountFooters = 0; Iterator kids = data.getChildren().iterator(); while (kids.hasNext()) { UIComponent kid = (UIComponent) kids.next(); if ((kid instanceof UIColumn) && kid.isRendered()) { returnVal.uiColumns.add((UIColumn) kid); if (getFacet(kid, "header") != null) returnVal.facetCountHeaders++; if (getFacet(kid, "footer") != null) returnVal.facetCountFooters++; } } return returnVal; } /** * Return an array of stylesheet classes to be applied to each column in the table in the order specified. Every column may or may not have a stylesheet. * * @param data * {@link UIData} component being rendered */ private String[] getColumnClasses(UIData data) { String values = (String) data.getAttributes().get("columnClasses"); if (values == null) { return (new String[0]); } values = values.trim(); ArrayList<String> list = new ArrayList<String>(); while (values.length() > 0) { int comma = values.indexOf(","); if (comma >= 0) { list.add(values.substring(0, comma).trim()); values = values.substring(comma + 1); } else { list.add(values.trim()); values = ""; } } String results[] = new String[list.size()]; return ((String[]) list.toArray(results)); } /** * Return an array of stylesheet classes to be applied to each row in the table, in the order specified. Every row may or may not have a stylesheet. * * @param data * {@link UIData} component being rendered */ private String[] getRowClasses(UIData data) { String values = (String) data.getAttributes().get("rowClasses"); if (values == null) { return (new String[0]); } values = values.trim(); ArrayList<String> list = new ArrayList<String>(); while (values.length() > 0) { int comma = values.indexOf(","); if (comma >= 0) { list.add(values.substring(0, comma).trim()); values = values.substring(comma + 1); } else { list.add(values.trim()); values = ""; } } String results[] = new String[list.size()]; return ((String[]) list.toArray(results)); } }