001/** 002 * Copyright 2005-2015 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.krad.uif.util; 017 018import org.apache.commons.lang.StringEscapeUtils; 019import org.apache.commons.lang.StringUtils; 020import org.apache.commons.lang.math.NumberUtils; 021import org.kuali.rice.core.api.util.type.TypeUtils; 022import org.springframework.util.Assert; 023 024import java.beans.PropertyDescriptor; 025import java.util.ArrayList; 026import java.util.List; 027import java.util.Map; 028import java.util.Set; 029 030/** 031 * Utility class for generating JavaScript 032 * 033 * @author Kuali Rice Team (rice.collab@kuali.org) 034 */ 035public class ScriptUtils { 036 037 /** 038 * Translates an Object to a String for representing the given Object as a JavaScript value 039 * 040 * <p> 041 * Handles null, List, Map, and Set collections, along with non quoting for numeric and boolean 042 * types. Complex types are treated as a String value using toString 043 * </p> 044 * 045 * @param value Object instance to translate 046 * @return JS value 047 */ 048 public static String translateValue(Object value) { 049 String jsValue = ""; 050 051 if (value == null) { 052 jsValue = "null"; 053 return jsValue; 054 } 055 056 if (value instanceof List) { 057 jsValue = "["; 058 059 List<Object> list = (List<Object>) value; 060 for (Object listItem : list) { 061 jsValue += translateValue(listItem); 062 jsValue += ","; 063 } 064 jsValue = StringUtils.removeEnd(jsValue, ","); 065 066 jsValue += "]"; 067 } else if (value instanceof Set) { 068 jsValue = "["; 069 070 Set<Object> set = (Set<Object>) value; 071 for (Object setItem : set) { 072 jsValue += translateValue(setItem); 073 jsValue += ","; 074 } 075 jsValue = StringUtils.removeEnd(jsValue, ","); 076 077 jsValue += "]"; 078 } else if (value instanceof Map) { 079 jsValue = "{"; 080 081 Map<Object, Object> map = (Map<Object, Object>) value; 082 for (Map.Entry<Object, Object> mapEntry : map.entrySet()) { 083 jsValue += "\"" + mapEntry.getKey().toString() + "\":"; 084 jsValue += translateValue(mapEntry.getValue()); 085 jsValue += ","; 086 } 087 jsValue = StringUtils.removeEnd(jsValue, ","); 088 089 jsValue += "}"; 090 } else { 091 Class<?> valueClass = value.getClass(); 092 if (TypeUtils.isSimpleType(valueClass) || TypeUtils.isClassClass(valueClass)) { 093 boolean quoteValue = true; 094 095 if (TypeUtils.isBooleanClass(valueClass) || 096 TypeUtils.isDecimalClass(valueClass) || 097 TypeUtils.isIntegralClass(valueClass)) { 098 quoteValue = false; 099 } 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("\"", """); 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("'", "'").replace(""", "\\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}