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 <c:forEach var="i" varStatus="rowStatus" items="${columns}" >
36 columns[<c:out value='${rowStatus.index}'/>] = {
37 visible: <c:out value='${i.visible}'/>,
38 name: "<c:out value='${i.name}'/>",
39 width: <c:out value='${i.width}'/> };
40 </c:forEach>
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 <mm:setJavascriptVar name="columns" value="${columns}" />
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 <mm:setJavascriptVar name="x" value="${y}" />
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 }