/* * � Copyright IBM Corp. 2014 * * Licensed under the Apache 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.apache.org/licenses/LICENSE-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 com.ibm.xsp.theme.bootstrap.renderkit.html.extlib.data; import java.io.IOException; import javax.faces.component.NamingContainer; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; import com.ibm.commons.util.StringUtil; import com.ibm.xsp.component.UIViewRootEx; import com.ibm.xsp.extlib.component.data.AbstractDataView; import com.ibm.xsp.extlib.component.data.UIDataView; import com.ibm.xsp.extlib.resources.ExtLibResources; import com.ibm.xsp.extlib.util.ExtLibUtil; import com.ibm.xsp.theme.bootstrap.resources.Resources; import com.ibm.xsp.theme.bootstrap.util.BootstrapUtil; import com.ibm.xsp.util.JSUtil; public class ForumViewRenderer extends com.ibm.xsp.extlib.renderkit.html_extended.data.ForumViewRenderer { protected static final int PROP_CHILDLISTICONCLASS = 300; protected static final int PROP_MEDIABODYCLASS = 301; protected static final int PROP_COLLAPSIBLEDIVCLASS = 302; @Override protected Object getProperty(int prop) { switch(prop) { case PROP_BLANKIMG: return Resources.get().BLANK_GIF; // note, for an Alt, there's a difference between the empty string and null case PROP_BLANKIMGALT: return ""; //$NON-NLS-1$ case PROP_ALTTEXTCLASS: return "lotusAltText"; // $NON-NLS-1$ case PROP_HEADERCLASS: return "clearfix"; // $NON-NLS-1$ case PROP_HEADERLEFTSTYLE: return null; case PROP_HEADERLEFTCLASS: return "pull-left"; // $NON-NLS-1$ case PROP_HEADERRIGHTSTYLE: return null; case PROP_HEADERRIGHTCLASS: return "pull-right"; // $NON-NLS-1$ case PROP_FOOTERCLASS: return "clearfix"; // $NON-NLS-1$ case PROP_FOOTERLEFTSTYLE: return null; case PROP_FOOTERLEFTCLASS: return "pull-left"; // $NON-NLS-1$ case PROP_FOOTERRIGHTSTYLE: return null; case PROP_FOOTERRIGHTCLASS: return "pull-right"; // $NON-NLS-1$ case PROP_SHOWICONDETAILSCLASS: return Resources.get().getIconClass("chevron-down"); // $NON-NLS-1$ case PROP_HIDEICONDETAILSCLASS: return Resources.get().getIconClass("chevron-up"); // $NON-NLS-1$ case PROP_MAINDIVCLASS: return "forumView"; // $NON-NLS-1$ case PROP_MAINLISTCLASS: return "media-list"; // $NON-NLS-1$ case PROP_CHILDLISTCLASS: return "xspForumChildList"; // $NON-NLS-1$ case PROP_MEDIABODYCLASS: return "media-body"; // $NON-NLS-1$ case PROP_LISTITEMCLASS: return "media"; // $NON-NLS-1$ case PROP_CHILDLISTICONCLASS: return "xspForumChildListIcon"; // $NON-NLS-1$ case PROP_COLLAPSIBLECONTENTSTYLE: return null; case PROP_COLLAPSIBLEDIVSTYLE: return "padding-left: 6px;"; // $NON-NLS-1$ case PROP_COLLAPSIBLEDIVCLASS: return "pull-right"; // $NON-NLS-1$ case PROP_TABLEHDRCOLIMAGE_SORTBOTH_ASCENDING: return Resources.get().VIEW_COLUMN_SORT_BOTH_ASCENDING; case PROP_TABLEHDRCOLIMAGE_SORTBOTH_DESCENDING: return Resources.get().VIEW_COLUMN_SORT_BOTH_DESCENDING; case PROP_TABLEHDRCOLIMAGE_SORTBOTH: return Resources.get().VIEW_COLUMN_SORT_NONE; case PROP_TABLEHDRCOLIMAGE_SORTED_ASCENDING: return Resources.get().VIEW_COLUMN_SORT_NORMAL; case PROP_TABLEHDRCOLIMAGE_SORTED_DESCENDING: return Resources.get().VIEW_COLUMN_SORT_REVERSE; case PROP_SUMMARYTITLECLASS: return "media-heading"; // $NON-NLS-1$ } return super.getProperty(prop); } @Override public void encodeBegin(FacesContext context, UIComponent component) throws IOException { // Encode the necessary resource UIViewRootEx rootEx = (UIViewRootEx)context.getViewRoot(); ExtLibResources.addEncodeResource(rootEx, ExtLibResources.extlibExtLib); super.encodeBegin(context, component); } @Override protected void startChildren(FacesContext context, ResponseWriter w, AbstractDataView c, ViewDefinition viewDef) throws IOException { w.startElement("div",c); // $NON-NLS-1$ String iconStyleClass = (String)getProperty(PROP_CHILDLISTICONCLASS); if(StringUtil.isNotEmpty(iconStyleClass)) { w.writeAttribute("class", iconStyleClass, null); // $NON-NLS-1$ } w.endElement("div"); // $NON-NLS-1$ w.startElement("ul",c); // $NON-NLS-1$ String style = (String)getProperty(PROP_CHILDLISTSTYLE); if(StringUtil.isNotEmpty(style)) { w.writeAttribute("style", style, null); // $NON-NLS-1$ } String styleClass = (String)getProperty(PROP_CHILDLISTCLASS); if(StringUtil.isNotEmpty(styleClass)) { w.writeAttribute("class", styleClass, null); // $NON-NLS-1$ } newLine(w); } @Override protected void startItem(FacesContext context, ResponseWriter w, AbstractDataView c, ViewDefinition viewDef, boolean emitId) throws IOException { w.startElement("li",c); // $NON-NLS-1$ if(emitId) { String id = viewDef.dataView.getClientId(context)+NamingContainer.SEPARATOR_CHAR+UIDataView.ROW_ID; w.writeAttribute("id", id, null); // $NON-NLS-1$ } String style = (String)getProperty(PROP_LISTITEMSTYLE); if(StringUtil.isNotEmpty(style)) { w.writeAttribute("style", style, null); // $NON-NLS-1$ } String styleClass = (String)getProperty(PROP_LISTITEMCLASS); if(StringUtil.isNotEmpty(styleClass)) { w.writeAttribute("class", styleClass, null); // $NON-NLS-1$ } newLine(w); } @Override protected void writeShowHideDetailContent(FacesContext context, ResponseWriter w, AbstractDataView c, ViewDefinition viewDef) throws IOException { if(!viewDef.hasSummary || !viewDef.hasDetail) { return; } // In case this is diabled for this particular row if(viewDef.rowDisableHideRow) { return; } boolean detailsOnClient = viewDef.detailsOnClient; String linkId = c.getClientId(context) + (viewDef.rowDetailVisible?HIDE_DELIMITER:SHOW_DELIMITER) + viewDef.rowPosition; w.startElement("a",c); w.writeAttribute("id",linkId,null); // $NON-NLS-1$ w.writeAttribute("href","javascript:;",null); // $NON-NLS-1$ $NON-NLS-2$ //LHEY97CCSZ adding the role=button w.writeAttribute("role", "button", null); // $NON-NLS-1$ // $NON-NLS-2$ String label = (String)getProperty(viewDef.rowDetailVisible ? PROP_HIDEICONDETAILSTOOLTIP : PROP_SHOWICONDETAILSTOOLTIP); if(StringUtil.isNotEmpty(label)) { w.writeAttribute("title", label, null); // $NON-NLS-1$ w.writeAttribute("aria-label", label, null); // $NON-NLS-1$ } if(detailsOnClient) { writeDetailsOnClientJS(context, w, c, viewDef); } String clazz = (String)getProperty(viewDef.rowDetailVisible?PROP_HIDEICONDETAILSCLASS:PROP_SHOWICONDETAILSCLASS); w.startElement("span",c); // $NON-NLS-1$ w.writeAttribute("class",clazz,null); // $NON-NLS-1$ String spanId = c.getClientId(context) + "_shChevron"; // $NON-NLS-1$ w.writeAttribute("id",spanId,null); // $NON-NLS-1$ // Defect 198012 - replace unnecessary title attribute with sr-only div containing text BootstrapUtil.renderIconTextForA11Y(w, label); w.endElement("span"); // $NON-NLS-1$ w.endElement("a"); if(!detailsOnClient) { if(viewDef.viewRowRefresh) { String refreshId = c.getClientId(context)+NamingContainer.SEPARATOR_CHAR+UIDataView.ROW_ID; setupSubmitOnClick(context, c, linkId, linkId, refreshId); } else { setupSubmitOnClick(context, c, linkId, linkId, null); } } } @Override protected void writeStandardRow(FacesContext context, ResponseWriter w, AbstractDataView c, ViewDefinition viewDef) throws IOException { if(!viewDef.singleRowRefresh) { int indent = getColumnIndentLevel(context, c, viewDef); // Fix the indentation if(indent<viewDef.indentLevel) { int limit = viewDef.indentLevel-indent; for(int i=0; i<limit; i++ ) { endChildren(context, w, c, viewDef); //endItem(context, w, c, viewDef); viewDef.indentLevel--; } } else if(indent>viewDef.indentLevel) { int limit = indent-viewDef.indentLevel; for(int i=0; i<limit; i++ ) { //startItem(context, w, c, viewDef, false); startChildren(context, w, c, viewDef); viewDef.indentLevel++; } } } // Start the new item to be displayed startItem(context, w, c, viewDef, true); // Use a table for IE as CSS float is largely buggy - Hope IE9 fixes this... if(viewDef.viewforumRenderAsTable) { w.startElement("table",c); // $NON-NLS-1$ w.writeAttribute("style", "margin: 0; padding:0; border-width: 0; width: 100%", null); // $NON-NLS-1$ $NON-NLS-2$ w.writeAttribute("role", "presentation", null); // $NON-NLS-1$ $NON-NLS-2$ w.startElement("tr",c); // $NON-NLS-1$ // Encode the row content w.startElement("td",c); // $NON-NLS-1$ w.writeAttribute("style", "width: 100%", null); // $NON-NLS-1$ $NON-NLS-2$ writeStandardContent(context, w, c, viewDef); w.endElement("td"); // $NON-NLS-1$ // Encode the expand/collapse details icon if(viewDef.collapsibleDetails) { w.startElement("td",c); // $NON-NLS-1$ w.writeAttribute("valign", "top", null); // $NON-NLS-1$ $NON-NLS-2$ //w.writeAttribute("style", "padding: -7px; width: 100%", null); writeShowHideDetailContent(context, w, c, viewDef); w.endElement("td"); // $NON-NLS-1$ } // And close the item... w.endElement("tr"); // $NON-NLS-1$ w.endElement("table"); // $NON-NLS-1$ } else { // If both the summary and the detail have to be in the client, we render them here if(!viewDef.rowDetailVisible) { //When detail is not visible, add the media-body div w.startElement("div",c); // $NON-NLS-1$ w.writeAttribute("class", (String)getProperty(PROP_MEDIABODYCLASS), null); // $NON-NLS-1$ } // Encode the row content writeStandardContent(context, w, c, viewDef); if(!viewDef.rowDetailVisible) { w.endElement("div"); // $NON-NLS-1$ } } endItem(context, w, c, viewDef); } @Override protected void writeStandardContent(FacesContext context, ResponseWriter w, AbstractDataView c, ViewDefinition viewDef) throws IOException { // If both the summary and the detail have to be in the client, we render them here writeSummary(context, w, c, viewDef); // Moved collapse/expand icon after summary, before detail to fix reading/tab order // for a11y. Used to be in writeStandardRow, just before writeStandardContent call // Encode the expand/collapse details icon if(viewDef.collapsibleDetails) { writeCollapsibleContent(context, w, c, viewDef); } writeDetail(context, w, c, viewDef); } @Override protected void writeCollapsibleContent(FacesContext context, ResponseWriter w, AbstractDataView c, ViewDefinition viewDef) throws IOException { // The style here is ensuring a float: left positioning w.startElement("div",c); // $NON-NLS-1$ String st2 = (String)getProperty(PROP_COLLAPSIBLEDIVSTYLE); if(StringUtil.isNotEmpty(st2)) { w.writeAttribute("style", st2, null); // $NON-NLS-1$ } String clazz = (String)getProperty(PROP_COLLAPSIBLEDIVCLASS); if(StringUtil.isNotEmpty(clazz)) { w.writeAttribute("class", clazz, null); // $NON-NLS-1$ } writeShowHideDetailContent(context, w, c, viewDef); w.endElement("div"); // $NON-NLS-1$ } @Override protected void writeSummaryTitleStyles(FacesContext context, ResponseWriter w, AbstractDataView c, ViewDefinition viewDef, String hideStyle) throws IOException { String style = viewDef.summaryColumn.getStyle(); if(StringUtil.isEmpty(style)) { style = (String)getProperty(PROP_SUMMARYTITLESTYLE); } style = ExtLibUtil.concatStyles(style, hideStyle); String styleClass = viewDef.summaryColumn.getStyleClass(); if(StringUtil.isEmpty(styleClass)) { styleClass = (String)getProperty(PROP_SUMMARYTITLECLASS); } if(StringUtil.isNotEmpty(styleClass)) { w.writeAttribute("class",styleClass,null); // $NON-NLS-1$ } w.writeAttribute("style",style,null); // $NON-NLS-1$ } @Override protected void writeShowHide(FacesContext context, ResponseWriter w, AbstractDataView c, ViewDefinition viewDef) throws IOException { //Disable superclass client show/hide JS, as bootstrap version implemented below //in writeDetailsOnClientJS and in xsp.mixin.js. But keep the hidden field if(viewDef.detailsOnClient && viewDef.collapsibleDetails) { writeClientShowHideHiddenField(context, w, c, viewDef); //addClientShowHideScript(context, w, c, viewDef); } } protected void writeDetailsOnClientJS(FacesContext context, ResponseWriter w, AbstractDataView c, ViewDefinition viewDef) throws IOException { //TODO re-work the hide/show detail on client JS in super classes to make this //a single reusable method across all data views and forum views //TODO Hacky clientId retrieval of the dataView control //replace with a proper getClientId method for the dataview //c.getClientId(context) gives back the id of the row String rowId = c.getClientId(context); String dataViewID = rowId.substring(0, rowId.lastIndexOf(":"+viewDef.dataModel.getRowIndex())); //Add JS onclick code to handle toggling aria-checked attribute StringBuilder onclick = new StringBuilder(); onclick.append("return XSP.xbtShowHideDetails("); // $NON-NLS-1$ JSUtil.addSingleQuoteString(onclick, dataViewID); onclick.append(", "); // $NON-NLS-1$ JSUtil.addSingleQuoteString(onclick, Integer.toString(viewDef.dataModel.getRowIndex())); onclick.append(", "); // $NON-NLS-1$ JSUtil.addSingleQuoteString(onclick, viewDef.rowPosition); onclick.append(", "); // $NON-NLS-1$ onclick.append(viewDef.summaryOrDetailVisible); onclick.append(", "); // $NON-NLS-1$ JSUtil.addSingleQuoteString(onclick, (String)getProperty(PROP_SHOWICONDETAILSCLASS)); onclick.append(", "); // $NON-NLS-1$ JSUtil.addSingleQuoteString(onclick, (String)getProperty(PROP_HIDEICONDETAILSCLASS)); onclick.append(", "); // $NON-NLS-1$ JSUtil.addSingleQuoteString(onclick, (String)getProperty(PROP_SHOWICONDETAILSTOOLTIP)); onclick.append(", "); // $NON-NLS-1$ JSUtil.addSingleQuoteString(onclick, (String)getProperty(PROP_HIDEICONDETAILSTOOLTIP)); onclick.append(")"); // $NON-NLS-1$ w.writeAttribute("onclick", onclick.toString(), null); // $NON-NLS-1$ // Build JS for onkeyup event to swap collapse/expand properties // when enter or space are pressed (see ExtLib.js) StringBuilder onkeydown = new StringBuilder(); onkeydown.append("var xbtIsTriggerKey = XSP.xbtIsTriggerKey(event);"); // $NON-NLS-1$ onkeydown.append("if(xbtIsTriggerKey){"); // $NON-NLS-1$ onkeydown.append("event.preventDefault();"); // $NON-NLS-1$ onkeydown.append("event.stopPropagation();"); // $NON-NLS-1$ onkeydown.append(onclick); onkeydown.append("}"); // $NON-NLS-1$ w.writeAttribute("onkeydown", onkeydown.toString(), null); // $NON-NLS-1$ } }