/** JSCumulativeContentRenderer.java. Purpose: Description: History: 10:35:15 AM Apr 22, 2015, Created by jumperchen Copyright (C) 2015 Potix Corporation. All Rights Reserved. */ package org.zkoss.zk.ui.sys; import java.io.IOException; import java.util.Date; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.zkoss.json.JSONAware; import org.zkoss.json.JSONs; import org.zkoss.lang.Generics; import org.zkoss.lang.Strings; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.UiException; /** * An implementation of {@link ContentRenderer} that renders * the content as a JavaScript property (i.e., name: ['value', 'value1', ..]) cumulatively. * It can support to add the same name with different value that the different value * will be packed into a list. * @author jumperchen * @since 8.0 */ public class JSCumulativeContentRenderer implements ContentRenderer { private Map<String, List<Object>> _stack = new LinkedHashMap<String, List<Object>>(); private List<Object> fetch(String name) { List<Object> list = _stack.get(name); if (list == null) { list = new LinkedList<Object>(); _stack.put(name, list); } return list; } public void render(String name, String value) throws IOException { fetch(name).add(renderValue(value)); } public void render(String name, Date value) throws IOException { fetch(name).add(renderValue(value)); } public void render(String name, Object value) throws IOException { fetch(name).add(renderValue(value)); } public void render(String name, int value) throws IOException { fetch(name).add(renderValue(value)); } public void render(String name, short value) throws IOException { fetch(name).add(renderValue(value)); } public void render(String name, long value) throws IOException { fetch(name).add(renderValue(value)); } public void render(String name, byte value) throws IOException { fetch(name).add(renderValue(value)); } public void render(String name, boolean value) throws IOException { fetch(name).add(renderValue(value)); } public void render(String name, double value) throws IOException { fetch(name).add(renderValue(value)); } public void render(String name, float value) throws IOException { fetch(name).add(renderValue(value)); } public void render(String name, char value) throws IOException { fetch(name).add(renderValue(value)); } private String renderValue(String value) { if (value == null) return null; else { return Strings.escapeJavaScript(value); } } private String renderValue(Date value) { if (value == null) return null; else return new StringBuilder().append("jq.j2d('").append(JSONs.d2j(value)).append("')").toString(); } private String renderValue(Component value) { if (value == null || value.getPage() == null) return null; else return new StringBuilder().append("{$u:'").append(value.getUuid()).append("'}").toString(); } private String renderValue(Object value) { if (value == null || value instanceof String) { return renderValue((String) value); } if (value instanceof Date) { return renderValue((Date) value); } if (value instanceof Component) { return renderValue((Component) value); } if (value instanceof Character) { return renderValue(((Character) value).charValue()); } StringBuilder buf = new StringBuilder(); if (value instanceof Map) { buf.append('{'); boolean first = true; for (Iterator it = ((Map) value).entrySet().iterator(); it.hasNext();) { final Map.Entry me = (Map.Entry) it.next(); if (first) first = false; else buf.append(','); buf.append('\'').append(me.getKey()).append("':"); renderValue(me.getValue()); } buf.append('}'); return buf.toString(); } if (value instanceof List) { buf.append('['); int j = 0; for (Iterator it = ((List) value).iterator(); it.hasNext(); j++) { if (j > 0) buf.append(','); renderValue(it.next()); } buf.append(']'); return buf.toString(); } //handle array if (value instanceof Object[]) { buf.append('['); final Object[] ary = (Object[]) value; for (int j = 0; j < ary.length; ++j) { if (j > 0) buf.append(','); renderValue(ary[j]); } buf.append(']'); return buf.toString(); } if (value instanceof int[]) { buf.append('['); final int[] ary = (int[]) value; for (int j = 0; j < ary.length; ++j) { if (j > 0) buf.append(','); renderValue(ary[j]); } buf.append(']'); return buf.toString(); } if (value instanceof long[]) { buf.append('['); final long[] ary = (long[]) value; for (int j = 0; j < ary.length; ++j) { if (j > 0) buf.append(','); renderValue(ary[j]); } buf.append(']'); return buf.toString(); } if (value instanceof short[]) { buf.append('['); final short[] ary = (short[]) value; for (int j = 0; j < ary.length; ++j) { if (j > 0) buf.append(','); renderValue(ary[j]); } buf.append(']'); return buf.toString(); } if (value instanceof float[]) { buf.append('['); final float[] ary = (float[]) value; for (int j = 0; j < ary.length; ++j) { if (j > 0) buf.append(','); renderValue(ary[j]); } buf.append(']'); return buf.toString(); } if (value instanceof double[]) { buf.append('['); final double[] ary = (double[]) value; for (int j = 0; j < ary.length; ++j) { if (j > 0) buf.append(','); renderValue(ary[j]); } buf.append(']'); return buf.toString(); } if (value instanceof byte[]) { buf.append('['); final byte[] ary = (byte[]) value; for (int j = 0; j < ary.length; ++j) { if (j > 0) buf.append(','); renderValue(ary[j]); } buf.append(']'); return buf.toString(); } if (value instanceof char[]) { buf.append('['); final char[] ary = (char[]) value; for (int j = 0; j < ary.length; ++j) { if (j > 0) buf.append(','); renderValue(ary[j]); } buf.append(']'); return buf.toString(); } if (value instanceof JSONAware) return ((JSONAware) value).toJSONString(); else return renderValue(value.toString()); } private String renderValue(char value) { StringBuilder buf = new StringBuilder(); buf.append('\''); switch (value) { case '\'': case '\\': buf.append('\\'); break; case '\n': buf.append('\\'); value = 'n'; break; case '\t': buf.append('\\'); value = 't'; break; case '\r': buf.append('\\'); value = 'r'; break; case '\f': buf.append('\\'); value = 'f'; break; } buf.append(value).append('\''); return buf.toString(); } public void renderDirectly(String name, Object value) { fetch(name).add(renderValue(value)); } public void renderWidgetListeners(Map<String, String> listeners) { fetch("listeners0").add(listeners); } public void renderWidgetOverrides(Map<String, String> overrides) { fetch("overrides").add(overrides); } public void renderWidgetAttributes(Map<String, String> attrs) { renderClientAttributes(attrs); } public void renderClientAttributes(Map<String, String> attrs) { fetch("domExtraAttrs").add(attrs); } public String toString() { StringBuilder sb = new StringBuilder(64); Map<String, List<Object>> result = new LinkedHashMap<String, List<Object>>(_stack); List<Map<String, String>> listeners = Generics.cast(result.remove("listeners0")); List<Map<String, String>> overrides = Generics.cast(result.remove("overrides")); List<Map<String, String>> attrs = Generics.cast(result.remove("domExtraAttrs")); for (Map.Entry<String, List<Object>> me : result.entrySet()) { renderName(sb, me.getKey()); sb.append('['); final List<Object> value = me.getValue(); int j = 0; for (Iterator<Object> it = value.iterator(); it.hasNext(); j++) { if (j > 0) sb.append(','); sb.append(it.next()); } sb.append(']'); } if (listeners != null) { renderName(sb, "listeners0"); sb.append('['); int j = 0; for (Iterator<Map<String, String>> it = listeners.iterator(); it.hasNext(); j++) { if (j > 0) sb.append(','); sb.append('{'); for (Iterator itt = it.next().entrySet().iterator(); itt.hasNext();) { final Map.Entry me = (Map.Entry) itt.next(); sb.append(me.getKey()).append(":function(event){\n").append(me.getValue()).append("\n},"); } sb.setCharAt(sb.length() - 1, '}'); } sb.append(']'); } if (overrides != null) { renderName(sb, "overrides"); sb.append('['); int j = 0; for (Iterator<Map<String, String>> it = overrides.iterator(); it.hasNext(); j++) { if (j > 0) sb.append(','); sb.append('{'); for (Iterator itt = it.next().entrySet().iterator(); itt.hasNext();) { final Map.Entry me = (Map.Entry) itt.next(); final String name = (String) me.getKey(); final String value = (String) me.getValue(); if (value != null) { //It is too costly to detect if it is a legal expression //so we only check the most common illegal case final String v = value.trim(); char cc; if (v.length() != 0 && ((cc = v.charAt(v.length() - 1)) == ';' || cc == ',' || (v.indexOf("function") < 0 && v.indexOf(';') >= 0))) throw new UiException("Illegal client override: " + v + (name.startsWith("on") ? "\nTo listen an event, remember to captalize the third letter, such as onClick" : "\nIt must be a legal JavaScript expression (not statement)")); } sb.append(name).append(":\n").append(value.length() == 0 ? "''" : value).append("\n,"); } sb.setCharAt(sb.length() - 1, '}'); } sb.append(']'); } if (attrs != null) { renderName(sb, "domExtraAttrs"); sb.append('['); int j = 0; for (Iterator<Map<String, String>> it = listeners.iterator(); it.hasNext(); j++) { if (j > 0) sb.append(','); sb.append('{'); for (Iterator itt = it.next().entrySet().iterator(); itt.hasNext();) { final Map.Entry me = (Map.Entry) itt.next(); renderValue(me.getKey()); //allow ':' or others sb.append(':'); renderValue(me.getValue()); sb.append("\n,"); } sb.setCharAt(sb.length() - 1, '}'); } sb.append(']'); } return sb.toString(); } private void renderName(StringBuilder sb, String name) { if (sb.length() > 0) sb.append(','); sb.append(name).append(':'); } }