/* * Copyright 2011-2012 Blazebit */ package com.blazebit.blazefaces.renderkit; import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.faces.FacesException; import javax.faces.application.Resource; import javax.faces.application.ResourceHandler; import javax.faces.component.UIComponent; import javax.faces.component.behavior.ClientBehavior; import javax.faces.component.behavior.ClientBehaviorHolder; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; import javax.faces.render.Renderer; import com.blazebit.blazefaces.behavior.ajax.AjaxBehavior; import com.blazebit.blazefaces.behavior.handler.EventHandler; import com.blazebit.blazefaces.component.AjaxSource; import com.blazebit.blazefaces.util.AjaxRequestBuilder; import com.blazebit.blazefaces.util.Constants; import com.blazebit.blazefaces.util.RendererUtils; import java.util.Collections; import javax.faces.component.UIParameter; import javax.faces.component.behavior.ClientBehaviorContext; public class CoreRenderer extends Renderer { protected void renderChildren(FacesContext facesContext, UIComponent component) throws IOException { for (Iterator<UIComponent> iterator = component.getChildren().iterator(); iterator.hasNext();) { UIComponent child = (UIComponent) iterator.next(); renderChild(facesContext, child); } } protected void renderChild(FacesContext facesContext, UIComponent child) throws IOException { if (!child.isRendered()) { return; } child.encodeBegin(facesContext); if (child.getRendersChildren()) { child.encodeChildren(facesContext); } else { renderChildren(facesContext, child); } child.encodeEnd(facesContext); } protected String getActionURL(FacesContext facesContext) { String actionURL = facesContext.getApplication().getViewHandler().getActionURL(facesContext, facesContext.getViewRoot().getViewId()); return facesContext.getExternalContext().encodeActionURL(actionURL); } protected String getResourceURL(FacesContext facesContext, String value) { if (value.contains(ResourceHandler.RESOURCE_IDENTIFIER)) { return value; } else { String url = facesContext.getApplication().getViewHandler().getResourceURL(facesContext, value); return facesContext.getExternalContext().encodeResourceURL(url); } } protected String getResourceRequestPath(FacesContext facesContext, String resourceName) { Resource resource = facesContext.getApplication().getResourceHandler().createResource(resourceName, "blazefaces"); return resource.getRequestPath(); } public boolean isPostback(FacesContext facesContext) { return facesContext.getRenderKit().getResponseStateManager().isPostback(facesContext); } public boolean isAjaxRequest(FacesContext context) { return context.getPartialViewContext().isAjaxRequest(); } protected void renderDataMapAttributes(FacesContext facesContext, UIComponent component) throws IOException { renderDataMapAttributes(facesContext, component, null); } @SuppressWarnings("unchecked") protected void renderDataMapAttributes(FacesContext facesContext, UIComponent component, Map<String, String> extraDataMap) throws IOException { ResponseWriter writer = facesContext.getResponseWriter(); Map<String, String> map = new HashMap<String, String>(); Object componentMap = component.getAttributes().get("dataMap"); if (extraDataMap != null) { map.putAll(extraDataMap); } if (componentMap != null) { if(!(componentMap instanceof Map)){ String mapText = componentMap.toString(); String[] entries = mapText.split(","); for(String entry : entries){ String[] keyValuePair = entry.split("="); if(keyValuePair.length != 2) throw new FacesException("Illegal dataMap String given on component with id: " + component.getId()); map.put(keyValuePair[0].trim(), keyValuePair[1].trim()); } }else{ map.putAll((Map<String, String>) componentMap); } } for (Map.Entry<String, String> entry : map.entrySet()) { if (shouldRenderAttribute(entry.getValue())) { writer.writeAttribute("data-" + entry.getKey(), entry.getValue(), null); } } } protected void renderPassThruAttributes(FacesContext facesContext, UIComponent component, String var, String[] attrs) throws IOException { ResponseWriter writer = facesContext.getResponseWriter(); for (String event : attrs) { String eventHandler = (String) component.getAttributes().get(event); if (eventHandler != null) { writer.write(var + ".addListener(\"" + event.substring(2, event.length()) + "\", function(e){" + eventHandler + ";});\n"); } } } protected void renderPassThruAttributes(FacesContext facesContext, UIComponent component, String[] attrs) throws IOException { ResponseWriter writer = facesContext.getResponseWriter(); for (String attribute : attrs) { Object value = component.getAttributes().get(attribute); if (shouldRenderAttribute(value)) { writer.writeAttribute(attribute, value.toString(), attribute); } } } protected void renderPassThruAttributes(FacesContext facesContext, UIComponent component, String[] attrs, String[] ignoredAttrs) throws IOException { ResponseWriter writer = facesContext.getResponseWriter(); for (String attribute : attrs) { if (isIgnoredAttribute(attribute, ignoredAttrs)) { continue; } Object value = component.getAttributes().get(attribute); if (shouldRenderAttribute(value)) { writer.writeAttribute(attribute, value.toString(), attribute); } } } private boolean isIgnoredAttribute(String attribute, String[] ignoredAttrs) { for (String ignoredAttribute : ignoredAttrs) { if (attribute.equals(ignoredAttribute)) { return true; } } return false; } 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; } protected boolean isPostBack() { FacesContext facesContext = FacesContext.getCurrentInstance(); return facesContext.getRenderKit().getResponseStateManager().isPostback(facesContext); } public String getEscapedClientId(String clientId) { return clientId.replaceAll(":", "\\\\\\\\:"); } public boolean isValueEmpty(String value) { if (value == null || "".equals(value)) { return true; } return false; } public boolean isValueBlank(String value) { if (value == null) { return true; } return value.trim().equals(""); } protected String addSubmitParam(String parent, String name, String value) { StringBuilder builder = new StringBuilder(); builder.append(".addSubmitParam('").append(parent).append("','").append(name).append("','").append(value).append("')"); return builder.toString(); } protected String buildAjaxRequest(FacesContext context, AjaxSource source, UIComponent form) { UIComponent component = (UIComponent) source; String clientId = component.getClientId(context); AjaxRequestBuilder builder = new AjaxRequestBuilder(); builder.source(context, component, clientId) .process(context, component, source.getProcess()) .update(context, component, source.getUpdate()) .async(source.isAsync()) .global(source.isGlobal()) .partialSubmit(source.isPartialSubmit(), source.isPartialSubmitSet()) .onstart(source.getOnstart()) .onerror(source.getOnerror()) .onsuccess(source.getOnsuccess()) .oncomplete(source.getOncomplete()) .params(component); if(form != null) { builder.form(form.getClientId(context)); } builder.preventDefault(); return builder.build(); } protected String buildNonAjaxRequest(FacesContext context, UIComponent component, UIComponent form, String decodeParam, boolean submit) { StringBuilder request = new StringBuilder(); String formId = form.getClientId(context); Map<String,String> params = new HashMap<String, String>(); if(decodeParam != null) { params.put(decodeParam, decodeParam); } for(UIComponent child : component.getChildren()) { if(child instanceof UIParameter) { UIParameter param = (UIParameter) child; params.put(param.getName(), String.valueOf(param.getValue())); } } //append params if(!params.isEmpty()) { request.append("BlazeFaces.addSubmitParam('").append(formId).append("',{"); for(Iterator<String> it = params.keySet().iterator(); it.hasNext();) { String key = it.next(); String value = params.get(key); request.append("'").append(key).append("':'").append(value).append("'"); if(it.hasNext()) request.append(","); } request.append("})"); } if(submit) { request.append(".submit('").append(formId).append("');"); } return request.toString(); } /** * Non-obstrusive way to apply client behaviors. * Behaviors are rendered as options to the client side widget and applied by widget to necessary dom element */ protected void encodeClientBehaviors(FacesContext context, ClientBehaviorHolder component) throws IOException { ResponseWriter writer = context.getResponseWriter(); //ClientBehaviors Map<String,List<ClientBehavior>> behaviorEvents = component.getClientBehaviors(); if(!behaviorEvents.isEmpty()) { String clientId = ((UIComponent) component).getClientId(context); List<ClientBehaviorContext.Parameter> params = Collections.emptyList(); writer.write(",behaviors:{"); for(Iterator<String> eventIterator = behaviorEvents.keySet().iterator(); eventIterator.hasNext();) { String event = eventIterator.next(); String domEvent = event; if(event.equalsIgnoreCase("valueChange")) //editable value holders domEvent = "change"; else if(event.equalsIgnoreCase("action")) //commands domEvent = "click"; writer.write(domEvent + ":"); writer.write("function(event) {"); for(Iterator<ClientBehavior> behaviorIter = behaviorEvents.get(event).iterator(); behaviorIter.hasNext();) { ClientBehavior behavior = behaviorIter.next(); ClientBehaviorContext cbc = ClientBehaviorContext.createClientBehaviorContext(context, (UIComponent) component, event, clientId, params); String script = behavior.getScript(cbc); //could be null if disabled if(script != null) { writer.write(script); } } writer.write("}"); if(eventIterator.hasNext()) { writer.write(","); } } writer.write("}"); } } protected void decodeBehaviors(FacesContext context, UIComponent component) { if(!(component instanceof ClientBehaviorHolder)) { return; } Map<String, List<ClientBehavior>> behaviors = ((ClientBehaviorHolder) component).getClientBehaviors(); if(behaviors.isEmpty()) { return; } Map<String, String> params = context.getExternalContext().getRequestParameterMap(); String behaviorEvent = params.get("javax.faces.behavior.event"); if(null != behaviorEvent) { List<ClientBehavior> behaviorsForEvent = behaviors.get(behaviorEvent); if(behaviorsForEvent != null && !behaviorsForEvent.isEmpty()) { String behaviorSource = params.get("javax.faces.source"); String clientId = component.getClientId(); if(behaviorSource != null && clientId.startsWith(behaviorSource)) { for(ClientBehavior behavior: behaviorsForEvent) { behavior.decode(context, component); } } } } } protected void startScript(ResponseWriter writer, String clientId) throws IOException { writer.startElement("script", null); writer.writeAttribute("id", clientId + "_s", null); writer.writeAttribute("type", "text/javascript", null); } protected void endScript(ResponseWriter writer) throws IOException { writer.endElement("script"); } /** * Duplicate code from json-simple project under apache license * http://code.google.com/p/json-simple/source/browse/trunk/src/org/json/simple/JSONValue.java */ protected String escapeText(String text) { if(text == null) { return null; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < text.length(); i++) { char ch = text.charAt(i); switch (ch) { case '"': sb.append("\\\""); break; case '\\': sb.append("\\\\"); break; case '\b': sb.append("\\b"); break; case '\f': sb.append("\\f"); break; case '\n': sb.append("\\n"); break; case '\r': sb.append("\\r"); break; case '\t': sb.append("\\t"); break; case '/': sb.append("\\/"); break; default: //Reference: http://www.unicode.org/versions/Unicode5.1.0/ if((ch >= '\u0000' && ch <= '\u001F') || (ch >= '\u007F' && ch <= '\u009F') || (ch >= '\u2000' && ch <= '\u20FF')) { String ss = Integer.toHexString(ch); sb.append("\\u"); for (int k = 0; k < 4 - ss.length(); k++) { sb.append('0'); } sb.append(ss.toUpperCase()); } else { sb.append(ch); } } } return sb.toString(); } protected String getOnclickBehaviors(FacesContext context, ClientBehaviorHolder cbh) { List<ClientBehavior> behaviors = cbh.getClientBehaviors().get("action"); StringBuilder sb = new StringBuilder(); if(behaviors != null && !behaviors.isEmpty()) { UIComponent component = (UIComponent) cbh; String clientId = component.getClientId(context); List<ClientBehaviorContext.Parameter> params = Collections.emptyList(); for(Iterator<ClientBehavior> behaviorIter = behaviors.iterator(); behaviorIter.hasNext();) { ClientBehavior behavior = behaviorIter.next(); ClientBehaviorContext cbc = ClientBehaviorContext.createClientBehaviorContext(context, component, "action", clientId, params); String script = behavior.getScript(cbc); if(script != null) sb.append(script).append(";"); } } return sb.length() == 0 ? null : sb.toString(); } protected void encodeBehaviors(FacesContext context, ClientBehaviorHolder component) throws IOException { Map<String, List<ClientBehavior>> behaviorEvents = component.getClientBehaviors(); if (!behaviorEvents.isEmpty()) { for (Iterator<String> eventIterator = behaviorEvents.keySet().iterator(); eventIterator.hasNext();) { String event = eventIterator.next(); EventHandler eh = new EventHandler(); eh.getAttributes().put("event", event); eh.getAttributes().put("for", ((UIComponent)component).getId()); for (Iterator<ClientBehavior> behaviorIter = behaviorEvents.get(event).iterator(); behaviorIter.hasNext();) { eh.addClientBehavior(null, behaviorIter.next()); } eh.encodeAll(context); } } } protected void encodeEventHandlers(FacesContext context, UIComponent component) throws IOException { for(UIComponent comp : component.getChildren()){ if(comp instanceof EventHandler){ comp.encodeAll(context); } } } protected boolean containsAjaxBehavior(FacesContext context, ClientBehaviorHolder comp) { Map<String, List<ClientBehavior>> map = comp.getClientBehaviors(); if(map == null) return false; for(List<ClientBehavior> list : map.values()){ for(ClientBehavior cb : list){ if(cb instanceof AjaxBehavior || cb instanceof javax.faces.component.behavior.AjaxBehavior) return true; } } return false; } }