/** * Copyright 2014-2017 Riccardo Massera (TheCoder4.Eu) and Stephan Rauh (http://www.beyondjava.net). * * This file is part of BootsFaces. * * 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 net.bootsfaces.component.button; import javax.faces.FacesException; import javax.faces.application.ConfigurableNavigationHandler; import javax.faces.application.NavigationCase; import javax.faces.application.ProjectStage; import javax.faces.component.*; import java.io.IOException; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; import javax.faces.render.FacesRenderer; import net.bootsfaces.C; import net.bootsfaces.component.icon.IconRenderer; import net.bootsfaces.render.CoreRenderer; import net.bootsfaces.render.H; import net.bootsfaces.render.Responsive; import net.bootsfaces.render.Tooltip; import net.bootsfaces.utils.BsfUtils; /** This class generates the HTML code of <b:button />. */ @FacesRenderer(componentFamily = "net.bootsfaces.component", rendererType = "net.bootsfaces.component.button.Button") public class ButtonRenderer extends CoreRenderer { /** * Renders the button. <br> * General layout of the generated HTML code:<br> * <button class="btn btn-large" href="#"%gt;<i * class="icon-star"></i> Star</button> * * @param context * the current FacesContext * @throws IOException * thrown if something's wrong with the ResponseWriter */ @Override public void encodeEnd(FacesContext context, UIComponent component) throws IOException { if (!component.isRendered()) { return; } Button button = (Button) component; encodeHTML(context, button); } /** * Encode the HTML code of the button. * * @param context * the current FacesContext * @throws IOException * thrown if something's wrong with the ResponseWriter */ public void encodeHTML(FacesContext context, Button button) throws IOException { boolean idHasBeenRendered=false; if (button.getHref()!=null&&button.getOutcome()!=null) throw new FacesException("Please define the href attribute or the outcome attribute, but not both"); ResponseWriter rw = context.getResponseWriter(); String clientId = button.getClientId(); // add responsive style String clazz = Responsive.getResponsiveStyleClass(button, false).trim(); boolean isResponsive = clazz.length() > 0; if (isResponsive) { rw.startElement("div", button); rw.writeAttribute("class", clazz, null); rw.writeAttribute("id", clientId, "id"); idHasBeenRendered = true; } Object value = (button.getValue() != null ? button.getValue() : ""); String style = button.getStyle(); String tag = "button"; if ((button.getHref() != null) || (button.getTarget() != null)) { tag="a"; } rw.startElement(tag, button); if (!idHasBeenRendered) { rw.writeAttribute("id", clientId, "id"); } rw.writeAttribute("name", clientId, "name"); if ("button".equals(tag)) { rw.writeAttribute("type", "button", null); } if(BsfUtils.isStringValued(button.getDir())) { rw.writeAttribute("dir", button.getDir(), "dir"); } if (style != null) { rw.writeAttribute("style", style, "style"); } if (button.getHref() != null) rw.writeAttribute("href", button.getHref(), "href"); if (button.getTarget() != null) rw.writeAttribute("target", button.getTarget(), "target"); rw.writeAttribute("class", getStyleClasses(button, isResponsive), "class"); Tooltip.generateTooltip(context, button, rw); final String clickHandler = encodeClick(context, button); if (null != clickHandler && clickHandler.length() > 0) { rw.writeAttribute("onclick", clickHandler, null); } if (BsfUtils.isStringValued(button.getDismiss())) { rw.writeAttribute("data-dismiss", button.getDismiss(), null); } if (button.isDisabled()) { rw.writeAttribute("disabled", "disabled", null); } // Encode attributes (HTML 4 pass-through + DHTML) renderPassThruAttributes(context, button, H.ALLBUTTON); String icon = button.getIcon(); String faicon = button.getIconAwesome(); boolean fa = false; // flag to indicate whether the selected icon set is // Font Awesome or not. if (faicon != null) { icon = faicon; fa = true; } if (icon != null) { Object ialign = button.getIconAlign(); // Default Left if (ialign != null && ialign.equals("right")) { rw.writeText(value + " ", null); IconRenderer.encodeIcon(rw, button, icon, fa, button.getIconSize(), button.getIconRotate(), button.getIconFlip(), button.isIconSpin(), null, null, false, false, false, false); } else { IconRenderer.encodeIcon(rw, button, icon, fa, button.getIconSize(), button.getIconRotate(), button.getIconFlip(), button.isIconSpin(), null, null, false, false, false, false); rw.writeText(" " + value, null); } } else { rw.writeText(value, null); } Tooltip.activateTooltips(context, button); rw.endElement(tag); if (isResponsive) { rw.endElement("div"); } } /** * Renders the Javascript code dealing with the click event. If the * developer provides their own onclick handler, is precedes the generated * Javascript code. * * @param context * The current FacesContext. * @param attrs * the attribute list * @return some Javascript code, such as * "window.location.href='/targetView.jsf';" */ private String encodeClick(FacesContext context, Button button) { String js; String userClick = button.getOnclick(); if (userClick != null) { js = userClick; } // +COLON; } else { js = ""; } String fragment = button.getFragment(); String outcome = button.getOutcome(); if (null != outcome && outcome.contains("#")) { if (null != fragment && fragment.length()>0) { throw new FacesException("Please define the URL fragment either in the fragment attribute or in the outcome attribute, but not both"); } int pos = outcome.indexOf("#"); fragment = outcome.substring(pos); outcome = outcome.substring(0, pos); } if (outcome == null || outcome.equals("")) { if (null != fragment && fragment.length()>0) { if (!fragment.startsWith("#")) { fragment = "#" + fragment; } js += "window.open('" + fragment + "', '"; if (button.getTarget() != null) js += button.getTarget(); else js += "_self"; js += "');"; return js; } } if (outcome == null || outcome.equals("") || outcome.equals("@none")) return js; if (canOutcomeBeRendered(button, fragment, outcome)) { outcome = (outcome == null) ? context.getViewRoot().getViewId() : outcome; String url = determineTargetURL(context, button, outcome); if (url != null) { if (url.startsWith("alert(")) { js=url; } else { if (fragment != null) { if (fragment.startsWith("#")) { url += fragment; } else { url += "#" + fragment; } } js += "window.open('" + url + "', '"; if (button.getTarget() != null) js += button.getTarget(); else js += "_self"; js += "');"; } } } return js; } /** * Do we have to suppress the target URL? * * @param attrs * the component's attribute list * @param fragment * the fragment of the URL behind the hash (outcome#fragment) * @param outcome * the value of the outcome attribute * @return true if the outcome can be rendered. */ private boolean canOutcomeBeRendered(Button button, String fragment, String outcome) { boolean renderOutcome = true; if (null == outcome && button.getAttributes() != null && button.getAttributes().containsKey("ng-click")) { String ngClick = (String)button.getAttributes().get("ng-click"); if (null != ngClick && (ngClick.length() > 0)) { if (fragment == null) { renderOutcome = false; } } } return renderOutcome; } /** * Translate the outcome attribute value to the target URL. * * @param context * the current FacesContext * @param outcome * the value of the outcome attribute * @return the target URL of the navigation rule (or the outcome if there's * not navigation rule) */ private String determineTargetURL(FacesContext context, Button button, String outcome) { ConfigurableNavigationHandler cnh = (ConfigurableNavigationHandler) context.getApplication() .getNavigationHandler(); NavigationCase navCase = cnh.getNavigationCase(context, null, outcome); /* * Param Name: javax.faces.PROJECT_STAGE Default Value: The default * value is ProjectStage#Production but IDE can set it differently in * web.xml Expected Values: Development, Production, SystemTest, * UnitTest Since: 2.0 * * If we cannot get an outcome we use an Alert to give a feedback to the * Developer if this build is in the Development Stage */ if (navCase == null) { if (FacesContext.getCurrentInstance().getApplication().getProjectStage().equals(ProjectStage.Development)) { return "alert('WARNING! " + C.W_NONAVCASE_BUTTON + "');"; } else { return ""; } } // throw new FacesException("The outcome '"+outcome+"' cannot be // resolved."); } String vId = navCase.getToViewId(context); Map<String, List<String>> params = getParams(navCase, button); String url; url = context.getApplication().getViewHandler().getBookmarkableURL(context, vId, params, button.isIncludeViewParams() || navCase.isIncludeViewParams()); return url; } /** * Find all parameters to include by looking at nested uiparams and params * of navigation case */ protected static Map<String, List<String>> getParams(NavigationCase navCase, Button button) { Map<String, List<String>> params = new LinkedHashMap<String, List<String>>(); // UIParams for (UIComponent child : button.getChildren()) { if (child.isRendered() && (child instanceof UIParameter)) { UIParameter uiParam = (UIParameter) child; if (!uiParam.isDisable()) { List<String> paramValues = params.get(uiParam.getName()); if (paramValues == null) { paramValues = new ArrayList<String>(); params.put(uiParam.getName(), paramValues); } paramValues.add(String.valueOf(uiParam.getValue())); } } } // NavCase Params Map<String, List<String>> navCaseParams = navCase.getParameters(); if (navCaseParams != null && !navCaseParams.isEmpty()) { for (Map.Entry<String, List<String>> entry : navCaseParams.entrySet()) { String key = entry.getKey(); // UIParams take precedence if (!params.containsKey(key)) { params.put(key, entry.getValue()); } } } return params; } /** * Collects the CSS classes of the button. * * @return the CSS classes (separated by a space). */ private static String getStyleClasses(Button button, boolean isResponsive) { StringBuilder sb; sb = new StringBuilder(40); // optimize int sb.append("btn"); String size = button.getSize(); if (size != null) { sb.append(" btn-").append(size); } String look = button.getLook(); if (look != null) { sb.append(" btn-").append(look); } else { sb.append(" btn-default"); } if (button.isDisabled()) { sb.append(" disabled"); } if (isResponsive) { sb.append(" btn-block"); } String sclass = button.getStyleClass(); if (sclass != null) { sb.append(" ").append(sclass); } return sb.toString().trim(); } /** * Detects whether an attribute is a default value or not. Method * temporarily copied from CoreRenderer. Should be replaced by a call of * CoreRenderer in the long run. * * @param value * the value to be checked * @return true if the value is not the default value */ protected boolean shouldRenderAttribute(Object value) { if (value == null) return false; if (value instanceof Boolean) { return ((Boolean) value).booleanValue(); } else if (value instanceof Number) { Number number = (Number) value; if (value instanceof Integer) return number.intValue() != Integer.MIN_VALUE; else if (value instanceof Double) return number.doubleValue() != Double.MIN_VALUE; else if (value instanceof Long) return number.longValue() != Long.MIN_VALUE; else if (value instanceof Byte) return number.byteValue() != Byte.MIN_VALUE; else if (value instanceof Float) return number.floatValue() != Float.MIN_VALUE; else if (value instanceof Short) return number.shortValue() != Short.MIN_VALUE; } return true; } }