View Javadoc
1   package com.randomnoun.common.webapp.taglib;
2   
3   /* (c) 2013 randomnoun. All Rights Reserved. This work is licensed under a
4    * BSD Simplified License. (http://www.randomnoun.com/bsd-simplified.html)
5    */
6   
7   import java.io.*;
8   import java.util.*;
9   
10  import jakarta.servlet.jsp.*;
11  import jakarta.servlet.jsp.tagext.*;
12  
13  import com.randomnoun.common.Struct;
14  import com.randomnoun.common.Text;
15  
16  /**
17   * Sets a javascript variable from a server-side resource or request attribute.
18   * The variable may contain any amount of maps or lists, which will be
19   * converted into the javascript equivalent.
20   *
21   * <p>Attributes defined for this tag (in common.tld) are:
22   * <ul>
23   * <li>name - the name of the javascript variable to define
24   * <li>value - the value of the javascript variable.
25   * </ul>
26   *
27   * <p>Both name and value may contain EL-style expressions.
28   *
29   * <p><i>e.g.</i> the following JSP snippet (taken from an early version of
30   * messageList.jsp) retrieves the 'columns' List from
31   * a request attribute, and sets it to a javascript variable of the same name:
32   *
33               <pre class="code">
34               var columns = new Array();
35               &lt;c:forEach var="i" varStatus="rowStatus" items="${columns}" &gt;
36                 columns[&lt;c:out value='${rowStatus.index}'/&gt;] = {
37                   visible: &lt;c:out value='${i.visible}'/&gt;,
38                   name: "&lt;c:out value='${i.name}'/&gt;",
39                   width: &lt;c:out value='${i.width}'/&gt; };
40               &lt;/c:forEach&gt;
41               </pre>
42   *
43   * <p>... which produces output of the form:
44   *
45               <pre class="code">
46               columns[0] = {
47                 visible: true,
48                 name: "externalMessageType",
49                 width: 91 };
50               columns[1] = {
51                 visible: true,
52                 name: "queue",
53                 width: 89 };
54               ...
55               </pre>
56   *
57   * <p>This JSP snippet can be reproduced with the following tag:
58   *
59               <pre class="code">
60               &lt;mm:setJavascriptVar name="columns" value="${columns}" /&gt;
61               </pre>
62   *
63   * <p>... which produces the slightly more terse, but functionally equivalent:
64   *
65               <pre class="code">
66               var columns = [{name: "externalMessageType",visible: true,width: 91}
67                 ,{name: "queue",visible: true,width: 89}
68                 ...
69               ]
70               ;
71               </pre>
72   *
73   *  This tag also translates arbitrary levels of maps and lists within objects passed
74   *  through to Javascript,
75   *
76   * <p><i>e.g.</i> this code sets a javascript variable 'x' to the value
77   * of the 'y' request attribute
78   *
79              <pre class="code">
80              &lt;mm:setJavascriptVar name="x" value="${y}" /&gt;
81              </pre>
82   *
83   * <p>For a reasonably complex 'y', this would generate the following
84   * HTML-embedded Javascript:
85   *
86              <pre class="code">
87              var x = ['list-string-element-1', 'list-string-element2',
88                       (key1: value1), (key2:value2),
89                        1234, 4321 ]
90              </pre>
91   *
92   * <p>The example above shows how string, map and numeric elements are
93   * represented within a list object.
94   *
95   * @author  knoxg
96   * 
97   */
98  public class SetJavascriptVarTag
99      extends BodyTagSupport
100 {
101     /** Generated serialVersionUID */
102 	private static final long serialVersionUID = -7010835090281695598L;
103 
104     /** The javscript name */
105     private String name;
106     
107     // * * The string entered in the value attribute of this tag */
108     //private String valueString;
109     
110     /** The calculated value of the java object to embed */ 
111     private Object value;
112 
113     /** The method in which dates are serialised to JSON */
114     private String jsonFormat;
115     
116     
117     /** Sets the name of the javascript variable to generate
118      * 
119      * @param name the name of the javascript variable to generate
120      */
121     public void setName(String name)
122     {
123         this.name = name;
124     }
125 
126     /**
127      * Gets the name of the javascript variable to generate
128      *
129      * @return the name of the javascript variable to generate
130      */
131     public String getName()
132     {
133         return name;
134     }
135 
136     /**
137      * Sets the object to convert into javascript 
138      *
139      * @param value the object to convert into javascript 
140      */
141     public void setValue(Object value)
142     {
143         this.value = value;
144     }
145 
146     /**
147      * Returns the object to convert into javascript 
148      *
149      * @return the object to convert into javascript 
150      */
151     public Object getValue()
152     {
153         return value;
154     }
155 
156     /** Sets the JSON format (e.g. method in which dates are serialised to JSON)
157      * 
158      * @param name the JSON format 
159      */
160     public void setJsonFormat(String jsonFormat)
161     {
162         this.jsonFormat = jsonFormat;
163     }
164 
165     /**
166      * Gets the JSON format (e.g. method in which dates are serialised to JSON)
167      *
168      * @return the JSON format
169      */
170     public String getJsonFormat()
171     {
172         return jsonFormat;
173     }
174 
175     /** Backwards-compatibility for old taglibs
176      * @see #setJsonFormat(String)
177      */
178     public void setDateFormat(String dateFormat) {
179         this.jsonFormat = dateFormat;
180     }
181 
182     /** Backwards-compatibility for old taglibs 
183      * @see #getJsonFormat()
184      */
185     public String getDateFormat() {
186         return jsonFormat;
187     }
188 
189     
190     /** doStart tag handler required to fulfill the Tag interface defined in the
191      * <a href="http://java.sun.com/products/jsp/">JSP specification</a>.
192      *
193      * This tag is always empty, and therefore must always
194      * return BodyTag.SKIP_BODY
195      *
196      * @return BodyTag.SKIP_BODY
197      */
198     @SuppressWarnings("rawtypes")
199 	public int doStartTag()
200         throws jakarta.servlet.jsp.JspException
201     {
202         try {
203         	JspWriter out = pageContext.getOut();
204 	        //String javascript;
205 	        // javascript = (name.indexOf(".")==-1 ? "var " : "") + name + " = ";
206         	out.append(name.indexOf(".")==-1 ? "var " : "");
207         	out.append(name);
208         	out.append(" = ");
209         	
210 	        if (value == null) {
211 	        	out.append("null");
212 	        } else if (value instanceof String) {
213 	        	out.append("\"");
214 	        	out.append(Text.escapeJavascript((String) value));
215 	        	out.append("\"");
216 	        } else if (value instanceof List) {
217 	        	// out.append(Struct.structuredListToJson((List) value, jsonFormat));
218 	        	Struct.structuredListToJson(out, (List) value, jsonFormat);
219 	        } else if (value instanceof Map) {
220 	        	// out.append(Struct.structuredMapToJson((Map) value, jsonFormat));
221 	        	Struct.structuredMapToJson(out, (Map) value, jsonFormat);
222 	        } else if (value instanceof Number) {
223 	        	out.append(String.valueOf(value));
224 	        } else if (value instanceof Boolean) {
225 	        	out.append(String.valueOf(value));
226 	        } else if (value instanceof java.util.Date) {
227             	// MS-compatible JSON encoding of Dates:
228             	// see http://weblogs.asp.net/bleroy/archive/2008/01/18/dates-and-json.aspx
229                 // javascript += "\"\\/Date(" + ((java.util.Date)value).getTime() +  ")\\/\"";
230 	        	out.append(Struct.toDate((java.util.Date) value, jsonFormat));
231 	        } else if (value instanceof Struct.WriteJsonFormat) {
232 	        	((Struct.WriteJsonFormat) value).writeJsonFormat(out, jsonFormat); 
233 	        } else if (value instanceof Struct.ToJsonFormat) {
234 	        	out.append(((Struct.ToJsonFormat) value).toJson(jsonFormat));
235 	        } else if (value instanceof Struct.ToJson) {
236 	        	out.append(((Struct.ToJson) value).toJson());
237 	        } else {
238 	        	throw new RuntimeException("Cannot translate Java object " + value.getClass().getName() + " to javascript variable");
239 	        }
240 	        out.append(";");
241 	        
242 	        // out.print(javascript);
243 		} catch (IOException ex) {
244 			// ignore these errors, since they can occur when the user hits 'stop' in their browser
245 		} catch (Throwable t) {
246 			// WAS does not log exceptions that occur within tag libraries; log and rethrow
247 			t.printStackTrace();
248 			throw (JspException) new JspException("Exception occurred in SetJavascriptVarTag").initCause(t);
249 		}
250 
251         return BodyTag.SKIP_BODY; // this tag always has an empty body.
252     }
253 
254     /** doEnd tag handler required to fulfill the Tag interface defined in the
255      * <a href="http://java.sun.com/products/jsp/">JSP specification</a>.
256      *
257      * <p>This method does nothing, and always returns BodyTag.EVAL_PAGE
258      *
259      * @return BodyTag.EVAL_PAGE
260      */
261     public int doEndTag()
262         throws jakarta.servlet.jsp.JspException
263     {
264         name = null;
265         value = null;
266         jsonFormat = null;
267 
268         return BodyTag.EVAL_PAGE;
269     }
270     
271 
272 }