/*
* � Copyright IBM Corp. 2014, 2015
*
* 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.outline.tree;
import java.io.IOException;
import java.util.Map;
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.outline.UIOutlineNavigator;
import com.ibm.xsp.extlib.renderkit.html_extended.outline.tree.HtmlListRenderer;
import com.ibm.xsp.extlib.resources.ExtLibResources;
import com.ibm.xsp.extlib.tree.ITreeNode;
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;
import com.ibm.xsp.util.TypedUtil;
public class MenuRenderer extends HtmlListRenderer {
public static final int TYPE_PILL = 0;
public static final int TYPE_LIST = 1;
private static final long serialVersionUID = 1L;
protected static final int PROP_MENU_SELECTED = 6;
protected static final int PROP_MENU_EXPANDED = 7;
protected static final int PROP_MENU_COLLAPSED = 8;
@Override
protected Object getProperty(int prop) {
switch(prop) {
case PROP_MENU_SELECTED: return "active"; // $NON-NLS-1$
case PROP_MENU_EXPANDED: return Resources.get().getIconClass("minus-sign"); // $NON-NLS-1$
case PROP_MENU_COLLAPSED: return Resources.get().getIconClass("plus-sign"); // $NON-NLS-1$
}
return super.getProperty(prop);
}
private boolean expandable;
private String expandEffect;
private int expandLevel;
private int type;
public MenuRenderer() {
}
public MenuRenderer(UIComponent component, int type) {
super(component);
this.type = type;
}
@Override
protected boolean renderCollapsedChildren() throws IOException {
// We render the children if the menu is exandable
return isExpandable();
}
public boolean isExpandable() {
return expandable;
}
public void setExpandable(boolean expandable) {
this.expandable = expandable;
}
public String getExpandEffect() {
return expandEffect;
}
public void setExpandEffect(String expandEffect) {
this.expandEffect = expandEffect;
}
public int getExpandLevel() {
return expandLevel;
}
public void setExpandLevel(int expandLevel) {
this.expandLevel = expandLevel;
}
@Override
protected boolean alwaysRenderItemLink(TreeContextImpl tree, boolean enabled, boolean selected) {
//return true;
return tree.getNode().getType()!=ITreeNode.NODE_CONTAINER;
}
@Override
protected String getContainerStyleClass(TreeContextImpl node) {
if(type==TYPE_LIST) {
return "nav nav-list"; // $NON-NLS-1$
}
return "nav nav-pills nav-stacked"; // $NON-NLS-1$
}
@Override
protected String getItemStyleClass(TreeContextImpl tree, boolean enabled, boolean selected) {
String clazz = super.getItemStyleClass(tree, enabled, selected);
if(tree.getNode().getType()==ITreeNode.NODE_CONTAINER) {
//clazz = "nav-header";
} else if(tree.getNode().getType()==ITreeNode.NODE_SEPARATOR) {
clazz = "divider"; // $NON-NLS-1$
}
if(!enabled) {
clazz = ExtLibUtil.concatStyleClasses(clazz, "disabled"); // $NON-NLS-1$
}
if(selected) {
clazz = ExtLibUtil.concatStyleClasses(clazz, "active"); // $NON-NLS-1$
}
return clazz;
}
@Override
protected void preRenderTree(FacesContext context, ResponseWriter writer, TreeContextImpl tree) throws IOException {
// Add the JS support if necessary
if(isExpandable()) {
UIViewRootEx rootEx = (UIViewRootEx) context.getViewRoot();
rootEx.setDojoTheme(true);
ExtLibResources.addEncodeResource(rootEx, Resources.bootstrapNavigator);
ExtLibResources.addEncodeResource(rootEx, ExtLibResources.extlibExtLib);
// Specific dojo effects
String effect = getExpandEffect();
if(StringUtil.isNotEmpty(effect)) {
rootEx.addEncodeResource(ExtLibResources.dojoFx);
ExtLibResources.addEncodeResource(rootEx, ExtLibResources.dojoFx);
}
}
super.preRenderTree(context, writer, tree);
}
@Override
protected void renderEntryItemContent(FacesContext context, ResponseWriter writer, TreeContextImpl tree, boolean enabled, boolean selected) throws IOException {
boolean section = tree.getNode().getType()!=ITreeNode.NODE_LEAF;
if(section && isExpandable()) {
if(selected) {
writer.writeAttribute("class", "selected",null); // $NON-NLS-1$ $NON-NLS-2$
}
//Containing div element with icon class
writer.startElement("div", null); // $NON-NLS-1$
int depth = tree.getDepth()-2;
UIComponent uiTree = tree.getComponent();
boolean keepState = false;
if (uiTree instanceof UIOutlineNavigator) {
keepState = ((UIOutlineNavigator)uiTree).isKeepState();
}
String nodeId = tree.getClientId(context, "node", tree.getDepth());//$NON-NLS-1$
boolean userExpanded = false;
boolean userCollapsed = false;
boolean expanded = depth<expandLevel && tree.getNode().isExpanded();
if (keepState) {
Map<String, String> params = TypedUtil.getRequestParameterMap(context.getExternalContext());
String value = params.get(nodeId);
if (!StringUtil.isEmpty(value)) {
if (value.equals("1")) { // $NON-NLS-1$
userExpanded = true;
}
else if (value.equals("0")) { // $NON-NLS-1$
userCollapsed = true;
}
}
}
String twistyLabel = "";
String twistyTitle = "";
String styleClass = "";
String expandedStyleClass = (String)getProperty(PROP_MENU_EXPANDED);
String collapsedStyleClass = (String)getProperty(PROP_MENU_COLLAPSED);
String expandedLabel = com.ibm.xsp.extlib.controls.ResourceHandler.getString("MenuRenderer.Expandedsection"); // $NON-NLS-1$
String collapsedLabel = com.ibm.xsp.extlib.controls.ResourceHandler.getString("MenuRenderer.Collapsedsection"); // $NON-NLS-1$
String expandedTitle = com.ibm.xsp.extlib.controls.ResourceHandler.getString("WidgetContainerRenderer.clicktocollapsethesection"); // $NON-NLS-1$
String collapsedTitle = com.ibm.xsp.extlib.controls.ResourceHandler.getString("WidgetContainerRenderer.clicktoexpandthesection"); // $NON-NLS-1$
if (userExpanded || expanded) {
styleClass = expandedStyleClass;
// "Expanded section"
twistyLabel = expandedLabel;
// "Click to collapse the section"
twistyTitle = expandedTitle;
}
else {
styleClass = collapsedStyleClass;
tree.getNodeContext().setHidden(true);
// "Collapsed section"
twistyLabel = collapsedLabel;
// "Click to expand the section"
twistyTitle = collapsedTitle;
}
writer.writeAttribute("class", ExtLibUtil.concatStyleClasses(styleClass,"navigator-twisty"),null); // $NON-NLS-2$ $NON-NLS-1$
writer.writeAttribute("role", "button",null); // $NON-NLS-1$ $NON-NLS-2$
writer.writeAttribute("tabindex","0",null); // $NON-NLS-1$ $NON-NLS-2$
writer.writeAttribute("aria-label", twistyLabel, null); // $NON-NLS-1$
writer.writeAttribute("title", twistyTitle, null); // $NON-NLS-1$
// Build JS for onclick event to swap collapse/expand properties (see Navigator.js)
StringBuilder onclick = new StringBuilder();
onclick.append("return XSP.xbtMenuSwap(event,"); // $NON-NLS-1$
JSUtil.addSingleQuoteString(onclick, getExpandEffect());
onclick.append(", "); // $NON-NLS-1$
JSUtil.addSingleQuoteString(onclick, nodeId); // $NON-NLS-1$
onclick.append(", "); // $NON-NLS-1$
JSUtil.addSingleQuoteString(onclick, collapsedStyleClass); // $NON-NLS-1$
onclick.append(", "); // $NON-NLS-1$
JSUtil.addSingleQuoteString(onclick, expandedStyleClass); // $NON-NLS-1$
onclick.append(", "); // $NON-NLS-1$
JSUtil.addSingleQuoteString(onclick, collapsedLabel); // $NON-NLS-1$
onclick.append(", "); // $NON-NLS-1$
JSUtil.addSingleQuoteString(onclick, expandedLabel); // $NON-NLS-1$
onclick.append(", "); // $NON-NLS-1$
JSUtil.addSingleQuoteString(onclick, collapsedTitle); // $NON-NLS-1$
onclick.append(", "); // $NON-NLS-1$
JSUtil.addSingleQuoteString(onclick, expandedTitle); // $NON-NLS-1$
onclick.append(");"); // $NON-NLS-1$
writer.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$
writer.writeAttribute("onkeydown", onkeydown.toString(), null); // $NON-NLS-1$
BootstrapUtil.renderIconTextForA11Y(writer, twistyLabel);
writer.endElement("div"); //$NON-NLS-1$
// Preserve user's Expanded/Collapsed state
if (keepState) {
writer.startElement("input", uiTree); // $NON-NLS-1$
writer.writeAttribute("type", "hidden", null); // $NON-NLS-1$ $NON-NLS-2$
writer.writeAttribute("id", nodeId, "id"); //$NON-NLS-1$ $NON-NLS-2$
writer.writeAttribute("name", nodeId, "name"); // $NON-NLS-1$ $NON-NLS-2$
if (userExpanded) {
writer.writeAttribute("value", "1", "value"); // $NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$
}
else if (userCollapsed) {
writer.writeAttribute("value", "0", "value"); // $NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$
}
else {
writer.writeAttribute("value", "", "value"); // $NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$
}
writer.endElement("input"); //$NON-NLS-1$
}
}
if(section) {
super.renderEntryItemContent(context, writer, tree, enabled, selected);
} else {
super.renderEntryItemContent(context, writer, tree, enabled, selected);
}
}
@Override
protected void renderEntryItemLinkAttributes(FacesContext context, ResponseWriter writer, TreeContextImpl tree, boolean enabled, boolean selected) throws IOException {
boolean section = tree.getNode().getType()!=ITreeNode.NODE_LEAF;
writer.writeAttribute("tabindex","0",null); // $NON-NLS-1$ $NON-NLS-2$
if(section) {
writer.writeAttribute("style","position: static; text-decoration:none; padding-left: 0",null); // $NON-NLS-1$ $NON-NLS-2$
} else {
super.renderEntryItemLinkAttributes(context, writer, tree, enabled, selected);
}
}
@Override
protected void renderEntryItemLabel(FacesContext context, ResponseWriter writer, TreeContextImpl tree, boolean enabled, boolean selected) throws IOException {
String label = tree.getNode().getLabel();
if(StringUtil.isNotEmpty(label)) {
boolean addDiv = tree.getNode().getType()!=ITreeNode.NODE_LEAF && isExpandable() && !hasLink(tree);
if(addDiv) {
writer.startElement("div", tree.getComponent()); // $NON-NLS-1$
String divStyle = "";
divStyle = "padding:10px;cursor:default;"; // $NON-NLS-1$
writer.writeAttribute("style", divStyle, null); // $NON-NLS-1$
}
writer.writeText(label, "label"); // $NON-NLS-1$
if(addDiv) {
writer.endElement("div"); // $NON-NLS-1$
}
}
}
@Override
protected boolean isChildrenSeparate() {
// We need the children to be generated in a separate <li>, else the lotusSelected class
// applies to the entire hierarchy
return true;
}
protected boolean hasLink(TreeContextImpl tree) {
String href = tree.getNode().getHref();
if(StringUtil.isNotEmpty(href)) {
return true;
} else {
String onclick = findNodeOnClick(tree);
if(StringUtil.isNotEmpty(onclick)) {
return true;
}
}
return false;
}
@Override
protected void renderEntryNodeChildAttributes(FacesContext context, ResponseWriter writer, TreeContextImpl tree, boolean enabled, boolean selected) throws IOException {
if(tree.getNodeContext().isHidden()) {
writer.writeAttribute("style","display:none",null); // $NON-NLS-1$ $NON-NLS-2$
}
}
@Override
protected String getItemRole(TreeContextImpl tree, boolean enabled, boolean selected) {
if(tree.getNode().getType()==ITreeNode.NODE_LEAF) {
return "treeitem"; // $NON-NLS-1$
}
return null;
}
@Override
protected void startRenderContainer(FacesContext context, ResponseWriter writer, TreeContextImpl tree) throws IOException {
String containerTag = getContainerTag();
if (StringUtil.isNotEmpty(containerTag)) {
writer.startElement(containerTag, null);
writer.writeAttribute("role", "tree", null); // $NON-NLS-1$ $NON-NLS-2$
// aria label
UIComponent component = tree.getComponent();
UIOutlineNavigator tcomponent = component instanceof UIOutlineNavigator ? (UIOutlineNavigator)component : null;
// "Navigation menu"
String ariaLabel = com.ibm.xsp.extlib.controls.ResourceHandler.getString("MenuRenderer.Navigationmenu"); // $NON-NLS-1$
if (tcomponent != null) {
ariaLabel = tcomponent.getAriaLabel();
}
if (StringUtil.isNotEmpty(ariaLabel)) {
writer.writeAttribute("aria-label", ariaLabel, null); // $NON-NLS-1$
}
String style = null;
String styleClass = null;
if (tree.getDepth() == 1) {
// ac: LHEY92PFY3 - A11Y | RPT | xc:viewMenu : ID values must be unique
if (!tree.isOuterTagEmitted()) {
String id = getClientId(context, tree);
if (StringUtil.isNotEmpty(id)) {
writer.writeAttribute("id", id, null); // $NON-NLS-1$
}
}
UIComponent c = tree.getComponent();
if (c != null) {
style = (String) c.getAttributes().get("style"); //$NON-NLS-1$
styleClass = (String) c.getAttributes().get("styleClass");//$NON-NLS-1$
}
}
style = ExtLibUtil.concatStyles(style, getContainerStyle(tree));
if (StringUtil.isNotEmpty(style)) {
writer.writeAttribute("style", style, null); // $NON-NLS-1$
}
styleClass = ExtLibUtil.concatStyleClasses(styleClass, getContainerStyleClass(tree));
if (StringUtil.isNotEmpty(styleClass)) {
writer.writeAttribute("class", styleClass, null); // $NON-NLS-1$
}
JSUtil.writeln(writer);
}
}
}