package ca.sqlpower.sql;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.sql.SQLException;
import java.util.List;
import java.util.ListIterator;
import ca.sqlpower.util.Web;
/**
* WebResultHTMLFormatter exists in order to format WebResultSets into
* an HTML table. A growing list of options are supported, controlled
* by calling the various get and set methods before a call to
* formatToStream. You may call formatToStream on the same instance
* as many times as you like; the output settings will remain in
* effect until you change them.
*
* @author Jonathan Fuerth
* @version $Id$
*/
public class WebResultHTMLFormatter extends WebResultFormatter {
private boolean dropdownsInline;
private boolean dropdownsAbove;
private int dropdownsPerRow;
private boolean rowHighlightingOn;
private String[] extraJavaScript;
private int repeatHeaderRow;
/**
* A javascript function that gets included in the output code if
* rowHighlightingOn is true at the time the format() method is
* called.
*/
private static final String JS_HIGHLIGHT_CODE =
"<script language=\"JavaScript\">\n"
+"// Derived from code found at javascript.faqts.com\n"
+"function highlightRow (boolean_element, h_color, n_color) {\n"
+" while (boolean_element.tagName.toUpperCase() != 'TR' && boolean_element != null)\n"
+" boolean_element = document.all?boolean_element.parentElement:boolean_element.parentNode;\n"
+" if (boolean_element) {\n"
+" if(boolean_element.value) {\n"
+" boolean_element.bgColor = h_color;\n"
+" } else {\n"
+" boolean_element.bgColor = n_color;\n"
+" }\n"
+" }\n"
+"}\n"
+"</script>\n";
/**
* Public constructor. Sets up defaults as follows:
* <pre>
* Dropdowns Inline = false;
* Dropdowns Above = true;
* Dropdowns Per Row = 3;
* Row Highlighting On = true;
* Extra JavaScript = null for all FieldTypes;
* Repeat Header Row = 0.
* </pre>
*/
public WebResultHTMLFormatter() {
super();
dropdownsInline=false;
dropdownsAbove=true;
dropdownsPerRow=3;
rowHighlightingOn=false;
extraJavaScript=new String[FieldTypes.LAST_TYPE];
repeatHeaderRow=0;
}
/**
* Gets the value of dropdownsInline.
*
* @return value of dropdownsInline.
*/
public boolean isDropdownsInline() {return dropdownsInline;}
/**
* Sets the value of dropdownsInline. DropdowsInline and
* DropdownsAbove are not mutually exclusive, so be sure to set
* them to opposite values unless you want two sets of dropdown
* boxes.
*
* @param v Value to assign to dropdownsInline.
*/
public void setDropdownsInline(boolean v) {this.dropdownsInline = v;}
/**
* Gets the value of dropdownsAbove.
*
* @return value of dropdownsAbove.
*/
public boolean isDropdownsAbove() {return dropdownsAbove;}
/**
* Sets the value of dropdownsAbove. DropdowsInline and
* DropdownsAbove are not mutually exclusive, so be sure to set
* them to opposite values unless you want two sets of dropdown
* boxes.
*
* @param v Value to assign to dropdownsAbove.
*/
public void setDropdownsAbove(boolean v) {this.dropdownsAbove = v;}
/**
* Gets the value of dropdownsPerRow.
*
* @return value of dropdownsPerRow.
*/
public int getDropdownsPerRow() {return dropdownsPerRow;}
/**
* Sets the value of dropdownsPerRow.
*
* @param v Value to assign to dropdownsPerRow.
*/
public void setDropdownsPerRow(int v) {this.dropdownsPerRow = v;}
/**
* Finds out if the Javascript row-highlighting code is enabled.
*
* @return true if row highlighting is enabled; false otherwise.
*/
public boolean isRowHighlightingOn() {return rowHighlightingOn;}
/**
* Sets the status of whether or not the row-highlighting
* Javascript code will be included in the generated HTML. Row
* highlighting will turn a row a different colour (controlled by
* the property "rowHighlightColour") when its radio box or
* checkbox is checked.
*
* @param v A value of true will enable row highlighting. False
* will disable it.
*/
public void setRowHighlightingOn(boolean v) {this.rowHighlightingOn = v;}
/**
* Gets the user-supplied extra javascript code that will be
* included in the resulting element's "onClick" event handler.
* This doesn't really apply to most element types.
*
* @param type The FieldTypes type that you wish to inquire on.
* @throws IndexOutOfBoundsException if the specified type is not
* in the range [0..FieldTypes.MAX_TYPE].
*/
public String getExtraJavaScript(int type) {
return extraJavaScript[type];
}
/**
* Sets the extra javascript code that will be included in the
* resulting element's "onClick" event handler. This doesn't
* really apply to most element types, and the existence of code
* in this property doesn't guarantee that this code will appear
* in the resulting HTML. For instance, extra javascript code
* associated with FieldTypes.TEXT has no effect.
*
* @param type The FieldTypes type that you wish to associate the
* javascript statements with.
* @throws IndexOutOfBoundsException if the specified type is not
* in the range [0..FieldTypes.MAX_TYPE].
*/
public void setExtraJavaScript(int type, String js) {
extraJavaScript[type]=js;
}
/**
* Get the current setting for the frequency of Header Row
* repetition. For instance, if repeatHeaderRow is 10, then every
* 10th row in the result table will be the header row.
*
* @return value of repeatHeaderRow.
*/
public int getRepeatHeaderRow() {
return repeatHeaderRow;
}
/**
* Set a new value for the frequency of Header Row repetition. For
* instance, if repeatHeaderRow is 10, then every 10th row in the
* result table will be the header row. A value of 0 disables
* header repetition.
*
* @param v Value to assign to repeatHeaderRow.
*/
public void setRepeatHeaderRow(int v) {
this.repeatHeaderRow = v;
}
public String format(WebResultSet wrs)
throws SQLException, NoRowidException, IllegalStateException {
StringWriter out=new StringWriter();
PrintWriter pwout=new PrintWriter(out);
formatToStream(wrs, pwout);
return out.toString();
}
public void formatToStream(WebResultSet wrs, PrintWriter out)
throws SQLException, NoRowidException, IllegalStateException {
//PrintWriter out=new PrintWriter(writer);
int numCols=wrs.getColumnCount();
StringBuffer sb=new StringBuffer(256);
int countdownToNextHeader=repeatHeaderRow;
if(countdownToNextHeader==0) {
countdownToNextHeader=-1;
}
if(rowHighlightingOn) {
out.print(JS_HIGHLIGHT_CODE);
}
if(dropdownsAbove) {
List choices=null;
int i=1;
int numRenderedCells=0;
// out.println("<table align=\"center\">");
out.println("<table>");
out.println(" <tr>");
while(i<=numCols) {
sb.setLength(0);
try {
choices=wrs.getColumnChoicesList(i);
if(choices != null) {
sb.append("\n <td align=\"right\" class=\"searchForm\">");
sb.append(wrs.getColumnLabel(i));
sb.append("</td>\n <td align=\"left\" class=\"searchForm\">");
sb.append(Web.makeSelectionList(
wrs.getColumnChoicesName(i),
choices,
wrs.getColumnDefaultChoice(i),
wrs.getColumnHasAny(i),
wrs.getColumnHasAll(i)));
sb.append("\n </td>");
numRenderedCells++;
if(numRenderedCells >= dropdownsPerRow) {
numRenderedCells=0;
sb.append("\n </tr>\n <tr>");
}
}
out.println(sb);
} catch(ColumnNotDisplayableException e) {
// Column didn't get printed (which is good)
}
i++;
}
out.println(" </tr>");
out.println("</table>");
}
// out.println("<table class=\"resultTable\" align=\"center\">");
out.println("<table class=\"resultTable\">");
outputHeaderRow(wrs, out);
if(dropdownsInline) {
out.println(" <tr class=\"resultTableHeading\">");
for(int i=1; i<=numCols; i++) {
sb.setLength(0);
try {
List choices=wrs.getColumnChoicesList(i);
sb.append(" <td>");
if(choices != null) {
sb.append(Web.makeSelectionList(
wrs.getColumnChoicesName(i),
choices,
wrs.getColumnDefaultChoice(i),
wrs.getColumnHasAny(i),
wrs.getColumnHasAll(i)));
}
sb.append("\n </td>");
out.println(sb);
} catch(ColumnNotDisplayableException e) {
// Column didn't get printed (which is good)
}
}
out.println(" </tr>");
}
StringBuffer align=new StringBuffer(10);
StringBuffer contents=new StringBuffer(50);
boolean mutexOnThisRow=false;
int mutexRowNum=0;
while(wrs.next()) {
out.println(" <tr class=\"resultTableData\">");
for(int i=1; i<=numCols; i++) {
sb.setLength(0);
if(wrs.getColumnType(i) == FieldTypes.MUTEX_CHECKBOX) {
try {
sb.append("<td align=\"center\">");
if(wrs.getString(i) != null) {
mutexOnThisRow=true;
sb.append("<input type=\"checkbox\" name=\"");
sb.append(wrs.getColumnLabel(i));
sb.append("\" value=\"");
sb.append(wrs.getRowid());
sb.append("\" onClick=\"");
sb.append(mutexBoxes(wrs.getColumnMutexList(i), mutexRowNum));
sb.append("\"");
if(wrs.getString(i).equals(checkboxYesValue)) {
sb.append(" checked");
}
sb.append(" />");
}
sb.append("</td>");
out.println(sb);
} catch(ColumnNotDisplayableException e) {
// Never happens
throw new IllegalStateException("Unexpected ColumnNotDisplayableException caught on MUTEX_CHECKBOX");
}
} else try {
boolean valueIsDefault = true;
String colDefault = wrs.getColumnDefaultValue(i);
String rawContents = wrs.getString(i);
align.setLength(0);
contents.setLength(0);
getColumnFormatted(wrs, i, contents, align);
if(colDefault != null && rawContents != null) {
valueIsDefault = rawContents.equals(colDefault);
}
sb.append(" <td align=\"");
sb.append(align);
sb.append("\">");
if(!valueIsDefault) {
sb.append("<font color=\"red\">");
}
sb.append(contents);
if(!valueIsDefault) {
sb.append("</font>");
}
sb.append("</td>");
out.println(sb);
} catch(ColumnNotDisplayableException e) {
// Column didn't get printed (which is good)
}
}
out.println(" </tr>");
countdownToNextHeader--;
if(countdownToNextHeader==0) {
outputHeaderRow(wrs, out);
countdownToNextHeader=repeatHeaderRow;
}
// Increment the rows-with-mutexes count if necessary
if(mutexOnThisRow) {
mutexRowNum++;
}
mutexOnThisRow=false;
}
out.println("</table>");
// Output dummy form-elements to make mutex checkboxes work
// if there was only one row.
if(mutexRowNum==1) {
for(int i=1; i<numCols; i++) {
try {
if(wrs.getColumnType(i) == FieldTypes.MUTEX_CHECKBOX) {
out.print("<input type=\"hidden\" name=\"");
out.print(wrs.getColumnLabel(i));
out.print("\" value=\"");
out.println("\" />");
}
} catch(ColumnNotDisplayableException e) {
// Never happens for MUTEX_CHECKBOX
throw new IllegalStateException("Unexpected ColumnNotDisplayableException on MUTEX_CHECKBOX");
}
}
}
out.flush();
}
protected StringBuffer mutexBoxes(List mutexList, int mutexRowNum) {
StringBuffer sb=new StringBuffer(40);
ListIterator it=mutexList.listIterator();
String curVal;
while(it.hasNext()) {
curVal=(String)it.next();
sb.append("form.").append(curVal).append("[")
.append(mutexRowNum).append("].checked=false; ");
}
return sb;
}
protected void getColumnFormatted(WebResultSet wrs,
int i,
StringBuffer contents,
StringBuffer align)
throws SQLException, NoRowidException, ColumnNotDisplayableException {
int type=wrs.getColumnType(i);
switch(type) {
case FieldTypes.RADIO:
if(wrs.getString(i) != null) {
align.append("center");
contents.append("<input type=\"radio\" name=\"")
.append(wrs.getColumnLabel(i))
.append("\" value=\"")
.append(wrs.getRowid())
.append("\" onClick=\"");
if(extraJavaScript[FieldTypes.RADIO] != null) {
contents.append(extraJavaScript[FieldTypes.RADIO]);
}
if(rowHighlightingOn) {
contents.append("; highlightRow(this, ")
.append("'00ff00',").append("'ff00ff'")
.append(")");
}
contents.append("; this.form.submit()\" />");
}
break;
case FieldTypes.CHECKBOX:
align.append("center");
if(wrs.getString(i) != null) {
contents.append("<input type=\"checkbox\" name=\"")
.append(wrs.getColumnLabel(i))
.append("\" value=\"")
.append(wrs.getRowid())
.append("\"");
if(extraJavaScript[FieldTypes.CHECKBOX] != null) {
contents.append(" onClick=\"")
.append(extraJavaScript[FieldTypes.RADIO])
.append("\"");
}
if(wrs.getString(i).equals(checkboxYesValue)) {
contents.append(" checked");
}
contents.append(" />");
}
break;
default:
super.getColumnFormatted(wrs, i, contents, align);
break;
}
}
protected void outputHeaderRow(WebResultSet wrs, PrintWriter out)
throws SQLException {
StringBuffer sb=new StringBuffer();
int numCols=wrs.getColumnCount();
out.println(" <tr class=\"resultTableHeading\">");
for(int i=1; i<=numCols; i++) {
sb.setLength(0);
int columnType = wrs.getColumnType(i);
try {
if(columnType != FieldTypes.DUMMY &&
columnType != FieldTypes.ROWID) {
sb.append(" <th valign=\"bottom\">");
sb.append(beautifyHeading(wrs.getColumnLabel(i)));
sb.append("</th>");
out.println(sb);
}
} catch(ColumnNotDisplayableException e) {
// Column didn't get printed (which is good)
}
}
out.println(" </tr>");
}
}