View Javadoc
1   /**
2    * Copyright 2005-2015 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                 jsValue += value.toString();
106 
107                 if (quoteValue) {
108                     jsValue += "\"";
109                 }
110             } else {
111                 // treat as data object
112                 jsValue = "{";
113                 Map<String, PropertyDescriptor> propertyDescriptors = ObjectPropertyUtils
114                         .getPropertyDescriptors(valueClass);
115                 for (String propertyName : propertyDescriptors.keySet()) {
116                     if (ObjectPropertyUtils.isReadableProperty(value, propertyName)
117                             && !"class".equals(propertyName)) {
118                         Object propertyValue = ObjectPropertyUtils.getPropertyValueAsText(value, propertyName);
119                         jsValue += propertyName + ":";
120                         jsValue += translateValue(propertyValue);
121                         jsValue += ",";
122                     }
123                 }
124                 jsValue = StringUtils.removeEnd(jsValue, ",");
125 
126                 jsValue += "}";
127             }
128         }
129 
130         return jsValue;
131     }
132 
133     /**
134      * Builds a JSON string form the given map
135      * 
136      * @param map map to translate
137      * @return String in JSON format
138      */
139     public static String toJSON(Map<String, String> map) {
140         StringBuffer sb = new StringBuffer("{");
141 
142         for (String key : map.keySet()) {
143             String optionValue = map.get(key);
144             if (sb.length() > 1) {
145                 sb.append(",");
146             }
147             sb.append("\"" + key + "\"");
148 
149             sb.append(":");
150             sb.append("\"" + escapeJSONString(optionValue) + "\"");
151         }
152         sb.append("}");
153 
154         return sb.toString();
155     }
156 
157     /**
158      * Escapes double quotes present in the given string
159      * 
160      * @param jsonString string to escape
161      * @return escaped string
162      */
163     public static String escapeJSONString(String jsonString) {
164         if (jsonString != null) {
165             jsonString = jsonString.replace("\"", "&quot;");
166         }
167 
168         return jsonString;
169     }
170 
171     /**
172      * Converts a map of string values to a json equivalent by converting the string values through
173      * the convertToJsValue(String) method; this will output a string representation of the map in
174      * json format
175      * 
176      * @param stringMap the map of String values to convert to a simple json object representation
177      * @return the json object as a string (for use in js)
178      */
179     public static String convertToJsValue(Map<String, String> stringMap) {
180         if (stringMap == null || stringMap.isEmpty()) {
181             return "{}";
182         }
183 
184         String convertedValue = "{";
185         for (String key : stringMap.keySet()) {
186             convertedValue = convertedValue + "\"" + key + "\":" + convertToJsValue(stringMap.get(key)) + ",";
187         }
188 
189         convertedValue = StringUtils.removeEnd(convertedValue, ",");
190         convertedValue = convertedValue + "}";
191 
192         return convertedValue;
193     }
194 
195     /**
196      * Convert a string to a javascript value - especially for use for options used to initialize
197      * widgets such as the tree and rich table
198      * 
199      * @param value the string to be converted
200      * @return the converted value
201      */
202     public static String convertToJsValue(String value) {
203 
204         // save input value to preserve any whitespace formatting
205         String originalValue = value;
206 
207         // remove whitespace for correct string matching
208         value = StringUtils.strip(value);
209 
210         // If an option value starts with { or [, it would be a nested value
211         // and it should not use quotes around it
212         if (StringUtils.startsWith(value, "{") || StringUtils.startsWith(value, "[")) {
213             return originalValue;
214         }
215         // need to be the base boolean value "false" is true in js - a non
216         // empty string
217         else if (value.equalsIgnoreCase("false") || value.equalsIgnoreCase("true")) {
218             return originalValue.toLowerCase();
219         }
220         // if it is a call back function, do not add the quotes
221         else if (StringUtils.startsWith(value, "function") && StringUtils.endsWith(value, "}")) {
222             return originalValue;
223         }
224         // for numerics
225         else if (NumberUtils.isNumber(value)) {
226             return originalValue;
227         } else {
228             // String values require double quotes
229             return "\"" + originalValue + "\"";
230         }
231     }
232 
233     /**
234      * Escapes the ' character present in collection names so it can be properly used in js without
235      * causing javascript errors due to an early completion of a ' string.
236      * 
237      * @param name name to escape
238      * @return name, with single quotes escape
239      */
240     public static String escapeName(String name) {
241         name = name.replace("'", "\\'");
242         return name;
243     }
244 
245     /**
246      * Converts a list of string to a valid js string array
247      * 
248      * @param list list of Strings to be converted
249      * @return String representing the js array
250      */
251     public static String convertStringListToJsArray(List<String> list) {
252         String array = "[";
253 
254         if (list != null) {
255             for (String s : list) {
256                 array = array + "'" + s + "',";
257             }
258             array = StringUtils.removeEnd(array, ",");
259         }
260         array = array + "]";
261 
262         return array;
263     }
264 
265     /**
266      * escapes a string using {@link org.apache.commons.lang.StringEscapeUtils#escapeHtml(String)}
267      * 
268      * <p>
269      * The apostrophe character is included as <code>StringEscapeUtils#escapeHtml(String)</code>
270      * does not consider it a legal entity.
271      * </p>
272      * 
273      * @param string the string to be escaped
274      * @return the escaped string - useful for embedding in server side generated JS scripts
275      */
276     public static String escapeHtml(String string) {
277         if (string == null) {
278             return null;
279         } else {
280             return StringEscapeUtils.escapeHtml(string).replace("'", "&apos;").replace("&quot;", "\\u0022");
281         }
282     }
283 
284     /**
285      * escape an array of strings
286      * 
287      * @param strings an array of strings to escape
288      * @return the array, with the strings escaped
289      */
290     public static List<String> escapeHtml(List<String> strings) {
291         if (strings == null) {
292             return null;
293         } else if (strings.isEmpty()) {
294             return strings;
295         } else {
296             List<String> result = new ArrayList<String>(strings.size());
297             for (String string : strings) {
298                 result.add(escapeHtml(string));
299             }
300             return result;
301         }
302     }
303 
304     /**
305      * Will append the second script parameter to the first if the first is not empty, also checks
306      * to see if the first script needs an end semi-colon added
307      * 
308      * @param script script that will be added to (null is allowed and converted to empty string)
309      * @param appendScript script to append
310      * @return String result of appending the two script parameters
311      */
312     public static String appendScript(String script, String appendScript) {
313         String appendedScript = script;
314         if (appendedScript == null) {
315             appendedScript = "";
316         } else if (StringUtils.isNotBlank(appendedScript) && !appendedScript.trim().endsWith(";")) {
317             appendedScript += "; ";
318         }
319 
320         appendedScript += appendScript;
321 
322         return appendedScript;
323     }
324 
325     /**
326      * Helper method to build a JS string that will invoke the given function with the given
327      * arguments
328      * 
329      * @param functionName name of the JS function to invoke
330      * @param arguments zero or more arguments to pass, each will be converted to the corresponding
331      *        JS type
332      * @return JS String for invoking the function
333      */
334     public static String buildFunctionCall(String functionName, Object... arguments) {
335         StringBuffer sb = new StringBuffer(functionName).append("(");
336 
337         if (arguments != null) {
338             List<String> jsArguments = new ArrayList<String>();
339             for (Object argument : arguments) {
340                 jsArguments.add(translateValue(argument));
341             }
342 
343             sb.append(StringUtils.join(jsArguments, ","));
344         }
345 
346         sb.append(");");
347 
348         return sb.toString();
349     }
350 
351     /**
352      * Builds the JavaScript string for binding the given script to the component with the given id
353      * for the given event name (using jQuery)
354      * 
355      * @param id id of the element to handle the event for
356      * @param eventName name of the event the script will handle
357      * @param eventScript script to be executed when the event is thrown, if blank an empty string
358      *        will be returned
359      * @return JS event handler script
360      */
361     public static String buildEventHandlerScript(String id, String eventName, String eventScript) {
362         if (StringUtils.isBlank(eventScript)) {
363             return "";
364         }
365 
366         Assert.hasLength(id, "Id is required for building event handler script");
367         Assert.hasLength(eventName, "Event name is required for building event handler script");
368 
369         StringBuffer sb = new StringBuffer();
370 
371         sb.append("jQuery('#" + id + "').on('");
372         sb.append(eventName);
373         sb.append("', function(e) {");
374         sb.append(eventScript);
375         sb.append("}); ");
376 
377         return sb.toString();
378     }
379 }