View Javadoc
1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.uif.util;
17  
18  import org.apache.commons.lang.StringEscapeUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.apache.commons.lang.math.NumberUtils;
21  import org.kuali.rice.core.api.util.type.TypeUtils;
22  import org.springframework.util.Assert;
23  
24  import java.beans.PropertyDescriptor;
25  import java.util.ArrayList;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Set;
29  
30  /**
31   * Utility class for generating JavaScript
32   * 
33   * @author Kuali Rice Team (rice.collab@kuali.org)
34   */
35  public class ScriptUtils {
36  
37      /**
38       * Translates an Object to a String for representing the given Object as a JavaScript value
39       * 
40       * <p>
41       * Handles null, List, Map, and Set collections, along with non quoting for numeric and boolean
42       * types. Complex types are treated as a String value using toString
43       * </p>
44       * 
45       * @param value Object instance to translate
46       * @return JS value
47       */
48      public static String translateValue(Object value) {
49          String jsValue = "";
50  
51          if (value == null) {
52              jsValue = "null";
53              return jsValue;
54          }
55  
56          if (value instanceof List) {
57              jsValue = "[";
58  
59              List<Object> list = (List<Object>) value;
60              for (Object listItem : list) {
61                  jsValue += translateValue(listItem);
62                  jsValue += ",";
63              }
64              jsValue = StringUtils.removeEnd(jsValue, ",");
65  
66              jsValue += "]";
67          } else if (value instanceof Set) {
68              jsValue = "[";
69  
70              Set<Object> set = (Set<Object>) value;
71              for (Object setItem : set) {
72                  jsValue += translateValue(setItem);
73                  jsValue += ",";
74              }
75              jsValue = StringUtils.removeEnd(jsValue, ",");
76  
77              jsValue += "]";
78          } else if (value instanceof Map) {
79              jsValue = "{";
80  
81              Map<Object, Object> map = (Map<Object, Object>) value;
82              for (Map.Entry<Object, Object> mapEntry : map.entrySet()) {
83                  jsValue += "\"" + mapEntry.getKey().toString() + "\":";
84                  jsValue += translateValue(mapEntry.getValue());
85                  jsValue += ",";
86              }
87              jsValue = StringUtils.removeEnd(jsValue, ",");
88  
89              jsValue += "}";
90          } else {
91              Class<?> valueClass = value.getClass();
92              if (TypeUtils.isSimpleType(valueClass) || TypeUtils.isClassClass(valueClass)) {
93                  boolean quoteValue = true;
94  
95                  if (TypeUtils.isBooleanClass(valueClass) ||
96                          TypeUtils.isDecimalClass(valueClass) ||
97                          TypeUtils.isIntegralClass(valueClass)) {
98                      quoteValue = false;
99                  }
100 
101                 if (quoteValue) {
102                     jsValue = "\"";
103                 }
104 
105                 // TODO: should this go through property editors?
106                 jsValue += value.toString();
107 
108                 if (quoteValue) {
109                     jsValue += "\"";
110                 }
111             } else {
112                 // treat as data object
113                 jsValue = "{";
114                 Map<String, PropertyDescriptor> propertyDescriptors = ObjectPropertyUtils
115                         .getPropertyDescriptors(valueClass);
116                 for (String propertyName : propertyDescriptors.keySet()) {
117                     if (ObjectPropertyUtils.isReadableProperty(value, propertyName)
118                             && !"class".equals(propertyName)) {
119                         Object propertyValue = ObjectPropertyUtils.getPropertyValue(value, propertyName);
120                         jsValue += propertyName + ":";
121                         jsValue += translateValue(propertyValue);
122                         jsValue += ",";
123                     }
124                 }
125                 jsValue = StringUtils.removeEnd(jsValue, ",");
126 
127                 jsValue += "}";
128             }
129         }
130 
131         return jsValue;
132     }
133 
134     /**
135      * Builds a JSON string form the given map
136      * 
137      * @param map map to translate
138      * @return String in JSON format
139      */
140     public static String toJSON(Map<String, String> map) {
141         StringBuffer sb = new StringBuffer("{");
142 
143         for (String key : map.keySet()) {
144             String optionValue = map.get(key);
145             if (sb.length() > 1) {
146                 sb.append(",");
147             }
148             sb.append("\"" + key + "\"");
149 
150             sb.append(":");
151             sb.append("\"" + escapeJSONString(optionValue) + "\"");
152         }
153         sb.append("}");
154 
155         return sb.toString();
156     }
157 
158     /**
159      * Escapes double quotes present in the given string
160      * 
161      * @param jsonString string to escape
162      * @return escaped string
163      */
164     public static String escapeJSONString(String jsonString) {
165         if (jsonString != null) {
166             jsonString = jsonString.replace("\"", "&quot;");
167         }
168 
169         return jsonString;
170     }
171 
172     /**
173      * Converts a map of string values to a json equivalent by converting the string values through
174      * the convertToJsValue(String) method; this will output a string representation of the map in
175      * json format
176      * 
177      * @param stringMap the map of String values to convert to a simple json object representation
178      * @return the json object as a string (for use in js)
179      */
180     public static String convertToJsValue(Map<String, String> stringMap) {
181         if (stringMap == null || stringMap.isEmpty()) {
182             return "{}";
183         }
184 
185         String convertedValue = "{";
186         for (String key : stringMap.keySet()) {
187             convertedValue = convertedValue + "\"" + key + "\":" + convertToJsValue(stringMap.get(key)) + ",";
188         }
189 
190         convertedValue = StringUtils.removeEnd(convertedValue, ",");
191         convertedValue = convertedValue + "}";
192 
193         return convertedValue;
194     }
195 
196     /**
197      * Convert a string to a javascript value - especially for use for options used to initialize
198      * widgets such as the tree and rich table
199      * 
200      * @param value the string to be converted
201      * @return the converted value
202      */
203     public static String convertToJsValue(String value) {
204 
205         // save input value to preserve any whitespace formatting
206         String originalValue = value;
207 
208         // remove whitespace for correct string matching
209         value = StringUtils.strip(value);
210 
211         // If an option value starts with { or [, it would be a nested value
212         // and it should not use quotes around it
213         if (StringUtils.startsWith(value, "{") || StringUtils.startsWith(value, "[")) {
214             return originalValue;
215         }
216         // need to be the base boolean value "false" is true in js - a non
217         // empty string
218         else if (value.equalsIgnoreCase("false") || value.equalsIgnoreCase("true")) {
219             return originalValue.toLowerCase();
220         }
221         // if it is a call back function, do not add the quotes
222         else if (StringUtils.startsWith(value, "function") && StringUtils.endsWith(value, "}")) {
223             return originalValue;
224         }
225         // for numerics
226         else if (NumberUtils.isNumber(value)) {
227             return originalValue;
228         } else {
229             // String values require double quotes
230             return "\"" + originalValue + "\"";
231         }
232     }
233 
234     /**
235      * Escapes the ' character present in collection names so it can be properly used in js without
236      * causing javascript errors due to an early completion of a ' string.
237      * 
238      * @param name name to escape
239      * @return name, with single quotes escape
240      */
241     public static String escapeName(String name) {
242         name = name.replace("'", "\\'");
243         return name;
244     }
245 
246     /**
247      * Converts a list of string to a valid js string array
248      * 
249      * @param list list of Strings to be converted
250      * @return String representing the js array
251      */
252     public static String convertStringListToJsArray(List<String> list) {
253         String array = "[";
254 
255         if (list != null) {
256             for (String s : list) {
257                 array = array + "'" + s + "',";
258             }
259             array = StringUtils.removeEnd(array, ",");
260         }
261         array = array + "]";
262 
263         return array;
264     }
265 
266     /**
267      * escapes a string using {@link org.apache.commons.lang.StringEscapeUtils#escapeHtml(String)}
268      * 
269      * <p>
270      * The apostrophe character is included as <code>StringEscapeUtils#escapeHtml(String)</code>
271      * does not consider it a legal entity.
272      * </p>
273      * 
274      * @param string the string to be escaped
275      * @return the escaped string - useful for embedding in server side generated JS scripts
276      */
277     public static String escapeHtml(String string) {
278         if (string == null) {
279             return null;
280         } else {
281             return StringEscapeUtils.escapeHtml(string).replace("'", "&apos;").replace("&quot;", "\\u0022");
282         }
283     }
284 
285     /**
286      * escape an array of strings
287      * 
288      * @param strings an array of strings to escape
289      * @return the array, with the strings escaped
290      */
291     public static List<String> escapeHtml(List<String> strings) {
292         if (strings == null) {
293             return null;
294         } else if (strings.isEmpty()) {
295             return strings;
296         } else {
297             List<String> result = new ArrayList<String>(strings.size());
298             for (String string : strings) {
299                 result.add(escapeHtml(string));
300             }
301             return result;
302         }
303     }
304 
305     /**
306      * Will append the second script parameter to the first if the first is not empty, also checks
307      * to see if the first script needs an end semi-colon added
308      * 
309      * @param script script that will be added to (null is allowed and converted to empty string)
310      * @param appendScript script to append
311      * @return String result of appending the two script parameters
312      */
313     public static String appendScript(String script, String appendScript) {
314         String appendedScript = script;
315         if (appendedScript == null) {
316             appendedScript = "";
317         } else if (StringUtils.isNotBlank(appendedScript) && !appendedScript.trim().endsWith(";")) {
318             appendedScript += "; ";
319         }
320 
321         appendedScript += appendScript;
322 
323         return appendedScript;
324     }
325 
326     /**
327      * Helper method to build a JS string that will invoke the given function with the given
328      * arguments
329      * 
330      * @param functionName name of the JS function to invoke
331      * @param arguments zero or more arguments to pass, each will be converted to the corresponding
332      *        JS type
333      * @return JS String for invoking the function
334      */
335     public static String buildFunctionCall(String functionName, Object... arguments) {
336         StringBuffer sb = new StringBuffer(functionName).append("(");
337 
338         if (arguments != null) {
339             List<String> jsArguments = new ArrayList<String>();
340             for (Object argument : arguments) {
341                 jsArguments.add(translateValue(argument));
342             }
343 
344             sb.append(StringUtils.join(jsArguments, ","));
345         }
346 
347         sb.append(");");
348 
349         return sb.toString();
350     }
351 
352     /**
353      * Builds the JavaScript string for binding the given script to the component with the given id
354      * for the given event name (using jQuery)
355      * 
356      * @param id id of the element to handle the event for
357      * @param eventName name of the event the script will handle
358      * @param eventScript script to be executed when the event is thrown, if blank an empty string
359      *        will be returned
360      * @return JS event handler script
361      */
362     public static String buildEventHandlerScript(String id, String eventName, String eventScript) {
363         if (StringUtils.isBlank(eventScript)) {
364             return "";
365         }
366 
367         Assert.hasLength(id, "Id is required for building event handler script");
368         Assert.hasLength(eventName, "Event name is required for building event handler script");
369 
370         StringBuffer sb = new StringBuffer();
371 
372         sb.append("jQuery('#" + id + "').on('");
373         sb.append(eventName);
374         sb.append("', function(e) {");
375         sb.append(eventScript);
376         sb.append("}); ");
377 
378         return sb.toString();
379     }
380 }