package org.sakaiproject.tool.gradebook.jsf.gradebookItemTable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.faces.component.UIColumn;
import javax.faces.component.UIComponent;
import javax.faces.component.UIData;
import javax.faces.component.html.HtmlGraphicImage;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.el.ValueBinding;
import javax.faces.render.Renderer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.myfaces.shared_impl.renderkit.html.HTML;
import org.sakaiproject.tool.gradebook.jsf.FacesUtil;
import org.sakaiproject.tool.gradebook.Category;
import org.sakaiproject.tool.gradebook.GradableObject;
import org.sakaiproject.tool.gradebook.ui.AssignmentGradeRow;
import org.sakaiproject.tool.cover.ToolManager;
public class GradebookItemTableRenderer extends Renderer {
protected static final Log log = LogFactory.getLog(GradebookItemTableRenderer.class);
private static final String CURSOR = "cursor:pointer";
private static final String EXPANDED_IMG = "/images/expand.gif";
private static final String COLLAPSED_IMG = "/images/collapse.gif";
private HtmlGraphicImage image;
private boolean expanded;
private String path;
private static final String EXPAND_ALT = "cat_expanded";
private static final String EXPAND_TITLE = "cat_view_collapse";
private static final String COLLAPSE_ALT = "cat_collapsed";
private static final String COLLAPSE_TITLE = "cat_view_expand";
String imgExpandAlt;
String imgExpandTitle;
String imgCollapseAlt;
String imgCollapseTitle;
/**
* This component renders its children
* @return true
*/
public boolean getRendersChildren()
{
return true;
}
public boolean supportsComponentType(UIComponent component)
{
return (component instanceof org.apache.myfaces.component.html.ext.HtmlDataTable);
}
public void encodeBegin(FacesContext context, UIComponent component) throws IOException
{
if(!component.isRendered()){
//tool_bar tag is not to be rendered, return now
return;
}
UIData data = (UIData) component;
//Begin Rendering
ResponseWriter writer = context.getResponseWriter();
//Render Header Facets
writer.startElement("table", data);
String styleClass = (String) data.getAttributes().get("styleClass");
if (styleClass != null) {
writer.writeAttribute("class", styleClass, "styleClass");
}
String cellspacing = (String) data.getAttributes().get("cellspacing");
if (cellspacing != null) {
writer.writeAttribute("cellspacing", cellspacing, null);
}
String cellpadding = (String) data.getAttributes().get("cellpadding");
if (cellpadding != null) {
writer.writeAttribute("cellpadding", cellpadding, null);
}
String headerClasses[] = getHeaderClasses(data);
int headerStyle = 0;
int headerStyles = headerClasses.length;
writer.writeText("\n", null);
String expandedValue = (String) data.getAttributes().get("expanded");
expanded = false;
if(expandedValue != null && expandedValue.equals("true"))
expanded = true;
else
expanded = false;
path = context.getExternalContext().getRequestContextPath();
imgExpandAlt = FacesUtil.getLocalizedString(EXPAND_ALT);
imgExpandTitle = FacesUtil.getLocalizedString(EXPAND_TITLE);
imgCollapseAlt = FacesUtil.getLocalizedString(COLLAPSE_ALT);
imgCollapseTitle = FacesUtil.getLocalizedString(COLLAPSE_TITLE);
image = new HtmlGraphicImage();
if (expanded) {
image.setValue(path + EXPANDED_IMG);
image.setAlt(imgExpandAlt);
image.setTitle(imgExpandTitle);
}
else {
image.setValue(path + COLLAPSED_IMG);
image.setAlt(imgCollapseAlt);
image.setTitle(imgCollapseTitle);
}
// Render the header facets (if any)
UIComponent header = data.getFacet("header");
int headerFacets = getFacetCount(data, "header");
if ((header != null) || (headerFacets > 0)) {
writer.startElement("thead", data);
writer.writeText("\n", null);
}
if (header != null) {
writer.startElement("tr", header);
writer.startElement("th", header);
writer.writeAttribute("colspan", "" + getColumnCount(data), null);
writer.writeAttribute("scope", "colgroup", null);
encodeRecursive(context, header);
writer.endElement("th");
writer.endElement("tr");
writer.writeText("\n", null);
}
if (headerFacets > 0) {
writer.startElement("tr", data);
writer.writeText("\n", null);
Iterator columns = getColumns(data);
while (columns.hasNext()) {
UIColumn column = (UIColumn) columns.next();
writer.startElement("th", column);
if (headerStyles > 0 && headerStyle < headerStyles) {
writer.writeAttribute("class", headerClasses[headerStyle++], "headerClass");
}
writer.writeAttribute("scope", "col", null);
if (column.getId().endsWith("_toggle")) {
// get the number of toggles for "expand/collapse all" js
int numItems = getNumDataItemsForToggle(context, component);
String onclickText = "javascript:showHideAll('" + numItems + "', '" + path + "', '" + imgExpandAlt + "', '" + imgCollapseAlt + "', '" + imgExpandTitle + "', '" + imgCollapseTitle + "');";
writer.startElement(HTML.IMG_ELEM, image);
writer.writeURIAttribute("src", image.getValue(), null);
writer.writeURIAttribute("onclick", onclickText, null);
writer.writeURIAttribute("style", CURSOR, null);
writer.writeURIAttribute("alt", image.getAlt(), null);
writer.writeURIAttribute("title", image.getTitle(), null);
writer.writeURIAttribute("id", "expandCollapseAll", null);
writer.endElement(HTML.IMG_ELEM);
}
UIComponent facet = column.getFacet("header");
if (facet != null) {
encodeRecursive(context, facet);
}
writer.endElement("th");
writer.writeText("\n", null);
}
writer.endElement("tr");
writer.writeText("\n", null);
}
if ((header != null) || (headerFacets > 0)) {
writer.endElement("thead");
writer.writeText("\n", null);
}
// Render the footer facets (if any)
UIComponent footer = data.getFacet("footer");
int footerFacets = getFacetCount(data, "footer");
String footerClass = (String) data.getAttributes().get("footerClass");
if ((footer != null) || (footerFacets > 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", "" + getColumnCount(data), null);
encodeRecursive(context, footer);
writer.endElement("td");
writer.endElement("tr");
writer.writeText("\n", null);
}
if (footerFacets > 0) {
writer.startElement("tr", data);
writer.writeText("\n", null);
Iterator columns = getColumns(data);
while (columns.hasNext()) {
UIColumn column = (UIColumn) columns.next();
writer.startElement("td", column);
if (footerClass != null) {
writer.writeAttribute("class", footerClass, "footerClass");
}
UIComponent facet = column.getFacet("footer");
if (facet != null) {
encodeRecursive(context, facet);
}
writer.endElement("td");
writer.writeText("\n", null);
}
writer.endElement("tr");
writer.writeText("\n", null);
}
if ((footer != null) || (footerFacets > 0)) {
writer.endElement("tfoot");
writer.writeText("\n", null);
}
}
/**
* We put all our processing in the encodeChildren method
* @param context
* @param component
* @throws IOException
*/
public void encodeChildren(FacesContext context, UIComponent component)
throws IOException
{
if ((context == null) || (component == null)) {
return;
}
if (!component.isRendered()) {
return;
}
UIData data = (UIData) component;
ValueBinding gbItemsBinding = component.getValueBinding("value");
List gbItemList = (List)gbItemsBinding.getValue(context);
// Set up variables we will need
String columnClasses[] = getColumnClasses(data);
int columnStyle = 0;
int columnStyles = columnClasses.length;
String rowClasses[] = getRowClasses(data);
int rowStyles = rowClasses.length;
ResponseWriter writer = context.getResponseWriter();
Iterator kids = null;
Iterator grandkids = null;
// Iterate over the rows of data that are provided
int processed = 0;
int rowIndex = data.getFirst() - 1;
int rows = data.getRows();
int rowStyle = 0;
writer.startElement("tbody", component);
writer.writeText("\n", null);
int hideDivNo = 0;
while (true) {
Category gbCategory = null;
boolean isCategory = false;
boolean isAssignment = false;
boolean isCourseGrade = false;
boolean isGradeRow = false;
if(gbItemList !=null && gbItemList.size() > (rowIndex+1) && rowIndex > -2)
{
Object gbItem = gbItemList.get(rowIndex+1);
if (gbItem instanceof Category) {
isCategory = true;
isCourseGrade = false;
isAssignment = false;
gbCategory = (Category) gbItem;
}
else if (gbItem instanceof GradableObject){
isCategory = false;
GradableObject go = (GradableObject) gbItem;
if (go.isAssignment()) {
isAssignment = true;
isCourseGrade = true;
}
else {
isAssignment = false;
isCourseGrade = true;
}
}
else if (gbItem instanceof AssignmentGradeRow) {
isCategory = false;
isCourseGrade = false;
isAssignment = false;
isGradeRow = true;
}
else {
throw new IllegalArgumentException("Invalid class passed to renderer: " + gbItem.getClass());
}
}
boolean hasChildBoolean = false;
int childNo = 0;
if(isCategory && gbCategory != null && gbItemList.size() > rowIndex + 2)
{
for (int i=rowIndex+2; i < gbItemList.size(); i++) {
Object nextItem = gbItemList.get(i);
if (nextItem instanceof GradableObject) {
GradableObject nextGo = (GradableObject) nextItem;
if (nextGo.isAssignment()) {
hasChildBoolean = true;
childNo++;
} else {
break;
}
}
else if (nextItem instanceof AssignmentGradeRow) {
hasChildBoolean = true;
childNo++;
} else if (nextItem instanceof Category) {
break;
}
}
}
// Have we displayed the requested number of rows?
if ((rows > 0) && (++processed > rows)) {
break;
}
// Select the current row
data.setRowIndex(++rowIndex);
if (!data.isRowAvailable()) {
break; // Scrolled past the last row
}
// Render the beginning of this row
writer.startElement("tr", data);
if(isAssignment || isGradeRow)
{
String assignId = "_id_" + new Integer(hideDivNo).toString() + "__hide_division_";
writer.writeAttribute("id", assignId, null);
if (!expanded)
writer.writeAttribute("style", "display:none;", null);
}
if (rowStyles > 0) {
writer.writeAttribute("class", rowClasses[rowStyle++],
"rowClasses");
if (rowStyle >= rowStyles) {
rowStyle = 0;
}
}
writer.writeText("\n", null);
// Iterate over the child UIColumn components for each row
columnStyle = 0;
kids = getColumns(data);
while (kids.hasNext()) {
// Identify the next renderable column
UIColumn column = (UIColumn) kids.next();
// Render the beginning of this cell
writer.startElement("td", column);
if (columnStyles > 0) {
writer.writeAttribute("class", columnClasses[columnStyle++],
"columnClasses");
if (columnStyle >= columnStyles) {
columnStyle = 0;
}
}
if((isAssignment || isCourseGrade || isCategory || isGradeRow)
&& column.getId().endsWith("_toggle"))
{
if(hasChildBoolean && isCategory)
{
String imgId = "_id_" + new Integer(hideDivNo).toString() + "__img_hide_division_";
StringBuffer hideTr = new StringBuffer();
for(int i=0; i<childNo; i++)
{
hideTr.append("javascript:showHideDiv('_id_").append(new Integer(hideDivNo+i).toString()).append("', '")
.append(path).append("', '").append(imgExpandAlt).append("', '").append(imgCollapseAlt).append("', '")
.append(imgExpandTitle).append("', '").append(imgCollapseTitle).append("');");
}
hideTr.append("mySetMainFrameHeight('Main").append(ToolManager.getCurrentPlacement().getId().replace('-','x')).append("');");
writer.startElement(HTML.IMG_ELEM, image);
writer.writeURIAttribute("src", image.getValue(), null);
writer.writeURIAttribute("onclick", hideTr, null);
writer.writeURIAttribute("style", CURSOR, null);
writer.writeURIAttribute("alt", image.getAlt(), null);
writer.writeURIAttribute("title", image.getTitle(), null);
writer.writeURIAttribute("id", imgId, null);
writer.endElement(HTML.IMG_ELEM);
}
writer.endElement("td");
continue;
}
// Render the contents of this cell by iterating over
// the kids of our kids
grandkids = getChildren(column);
while (grandkids.hasNext()) {
encodeRecursive(context, (UIComponent) grandkids.next());
}
// Render the ending of this cell
writer.endElement("td");
writer.writeText("\n", null);
}
// Render the ending of this row
writer.endElement("tr");
writer.writeText("\n", null);
if(isAssignment || isGradeRow)
{
if(expanded)
{
writer.write("<script type=\"text/javascript\">");
writer.write(" showHideDiv('_id_" + new Integer(hideDivNo).toString() +
"', '" + path + "', '" + imgExpandAlt + "', '" + imgCollapseAlt + "', '" + imgExpandTitle + "', '" + imgCollapseTitle + "');");
writer.write("</script>");
}
hideDivNo++;
}
}
writer.endElement("tbody");
writer.writeText("\n", null);
// 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)) {
return;
}
if (!component.isRendered()) {
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);
}
/**
* <p>Return an Iterator over the <code>UIColumn</code> children
* of the specified <code>UIData</code> that have a
* <code>rendered</code> property of <code>true</code>.</p>
*
* @param data <code>UIData</code> for which to extract children
*/
private Iterator getColumns(UIData data) {
List results = new ArrayList();
Iterator kids = data.getChildren().iterator();
while (kids.hasNext()) {
UIComponent kid = (UIComponent) kids.next();
if ((kid instanceof UIColumn) && kid.isRendered()) {
results.add(kid);
}
}
return (results.iterator());
}
/**
* <p>Return the number of child <code>UIColumn</code> components
* nested in the specified <code>UIData</code> that have a facet with
* the specified name.</p>
*
* @param data <code>UIData</code> component being analyzed
* @param name Name of the facet being analyzed
*/
private int getFacetCount(UIData data, String name) {
int n = 0;
Iterator kids = getColumns(data);
while (kids.hasNext()) {
UIComponent kid = (UIComponent) kids.next();
if (kid.getFacet(name) != null) {
n++;
}
}
return (n);
}
protected Iterator getChildren(UIComponent oComponent){
List oResults = new ArrayList();
for(Iterator oKids = oComponent.getChildren().iterator(); oKids.hasNext();){
UIComponent oKid = (UIComponent)oKids.next();
if(oKid.isRendered())
oResults.add(oKid);
}
return oResults.iterator();
}
private void encodeRecursive(FacesContext oContext,UIComponent oComponent) throws IOException{
if (!oComponent.isRendered()){
return;
}
oComponent.encodeBegin(oContext);
if (oComponent.getRendersChildren()){
oComponent.encodeChildren(oContext);
}
else{
Iterator oChildren = getChildren(oComponent);
for(;oChildren.hasNext();){
UIComponent oChildComponent = (UIComponent)oChildren.next();
encodeRecursive(oContext,oChildComponent);
}
}
oComponent.encodeEnd(oContext);
}
/**
* <p>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 class.</p>
*
* @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 list = new ArrayList();
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 = "";
}
}
// now iterate through the columns and remove the class for any
// columns that aren't rendered
List columns = data.getChildren();
int listIndex = 0;
for (int i=0; i < columns.size(); i++) {
if (i >= list.size())
break;
UIComponent kid = (UIComponent) columns.get(i);
if ((kid instanceof UIColumn) && !kid.isRendered()) {
list.remove(listIndex);
} else {
listIndex++;
}
}
String results[] = new String[list.size()];
return ((String[]) list.toArray(results));
}
/**
* <p>Return an array of stylesheet classes to be applied to
* each header in the table in the order specified. Every column may or
* may not have a class.</p>
*
* @param data {@link UIData} component being rendered
*/
private String[] getHeaderClasses(UIData data) {
String values = (String) data.getAttributes().get("headerClasses");
if (values == null) {
return (new String[0]);
}
values = values.trim();
ArrayList list = new ArrayList();
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 = "";
}
}
// now iterate through the columns and remove the class for any
// columns that aren't rendered
List columns = data.getChildren();
int listIndex = 0;
for (int i=0; i < columns.size(); i++) {
if (i >= list.size())
break;
UIComponent kid = (UIComponent) columns.get(i);
if ((kid instanceof UIColumn) && !kid.isRendered()) {
list.remove(listIndex);
} else {
listIndex++;
}
}
String results[] = new String[list.size()];
return ((String[]) list.toArray(results));
}
/**
* <p>Return the number of child <code>UIColumn</code> components
* that are nested in the specified {@link UIData}.</p>
*
* @param data {@link UIData} component being analyzed
*/
private int getColumnCount(UIData data) {
int columns = 0;
Iterator kids = getColumns(data);
while (kids.hasNext()) {
UIComponent kid = (UIComponent) kids.next();
columns++;
}
return (columns);
}
/**
* <p>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.</p>
*
* @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 list = new ArrayList();
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));
}
private int getNumDataItemsForToggle(FacesContext context, UIComponent component) {
int numDataItems = 0;
ValueBinding gbItemsBinding = component.getValueBinding("value");
List gbItemList = (List)gbItemsBinding.getValue(context);
if (gbItemList != null && !gbItemList.isEmpty()) {
numDataItems = gbItemList.size();
}
return numDataItems;
}
}