1 /** 2 * @fileOverview A collection of miscellaneous utilities. 3 */ 4 5 if (!appjet) { 6 throw new Error('appjet library is required for util library.'); 7 } 8 9 /** 10 * Returns a string version of the MD5 signature of x. 11 * 12 * @example print(md5("appjet")); // prints "9b458805f67473b49761c13e48c5de35" 13 * 14 * @param {String} x a string 15 * @return {String} the md5 hash of x 16 */ 17 function md5(x) { 18 return appjet._native.md5(x); 19 } 20 21 /** 22 * Iterator convenience for JavaScript Objects. 23 * 24 * Note that if func returns false, the iteration will be immediately terminated. 25 * (Returning undefined, or not specifying a return type, does not terminate the iteration). 26 * 27 * @example 28 var pastels = { 29 red: "#fcc", 30 green: "#cfc", 31 blue: "#ccf" 32 }; 33 eachProperty(pastels, function(key, value) { 34 print(DIV({style: 'background: '+value+';'}, key)); 35 }); 36 * 37 * @param {object} obj The object over which to iterate. 38 * @param {function} func The function to run on each [key,value] pair. 39 */ 40 function eachProperty(obj, func) { 41 var r; 42 for (k in obj) { 43 if (obj.hasOwnProperty(k)) { 44 r = func(k,obj[k]); 45 if (r === false) { 46 break; 47 } 48 } 49 } 50 } 51 52 /** 53 * Douglas Crockford's "object" function for prototypal inheritance, taken from 54 * http://javascript.crockford.com/prototypal.html 55 * 56 * @param {object} parent The parent object. 57 * @return {object} A new object whose prototype is parent. 58 */ 59 function object(parent) { 60 function f() {}; 61 f.prototype = parent; 62 return new f(); 63 } 64 65 /** 66 * Creates an array of the properties of <code>obj</code>, 67 * <em>not</em> including built-in or inherited properties. If no 68 * argument is given, applies to the global object. 69 * 70 * @example 71 // Prints "abc" 72 keys({a: 1, b: 2, c: 3}).forEach(function(k) { 73 print(k); 74 } 75 * 76 * @example 77 // Prints all the functions and object members of the global "appjet" object, 78 // one per line. 79 print(keys(appjet).join('\n')); 80 * 81 * @param {object} obj 82 */ 83 function keys(obj) { 84 var array = []; 85 var o = obj; 86 if (o == undefined) { 87 o = this; 88 } 89 for(var k in o) { 90 if (o.hasOwnProperty(k)) { 91 array.push(k); 92 } 93 } 94 return array; 95 } 96 97 /** 98 * Comparator that returns -1, +1, or 0 depending on whether a<b, or a>b, or 99 * neither, respectively. 100 * @param {object} a 101 * @param {object} b 102 * @return {number} -1, 0, or +1 103 */ 104 function cmp(a,b) { 105 if (a < b) { 106 return -1; 107 } 108 if (a > b) { 109 return 1; 110 } 111 return 0; 112 } 113 114 /** 115 * Removes leading and trailing whitespace from a string. 116 * @param {string} str 117 * @return {string} The trimmed string. 118 */ 119 function trim(str) { 120 return str.replace(/^\s+|\s+$/g, ""); 121 } 122 123 //---------------------------------------------------------------- 124 // string set 125 //---------------------------------------------------------------- 126 127 // TODO: unit-test the string set, or just switch it over to 128 // java.util.HashSet or something. 129 130 /** 131 * Constructor for an object that holds a set of strings, with basic 132 * set operations. This is better for keeping track of a set than 133 * using an associative arrays, because it avoids key collisions with 134 * javascript built-ins like 'toString', 'class', etc. 135 * 136 * @constructor 137 * @param {string} initialString1 138 * @param {string} initialString2 139 * @param {string} etc ... 140 * 141 */ 142 StringSet = function(initialElement1, initialElement2, etc) { 143 this._obj = {}; 144 for (var i = 0; i < arguments.length; i++) { 145 if (typeof(arguments[i]) != 'string') { 146 throw new Error('StringSet constructor must take only string arguments.'); 147 } else { 148 this._obj['$$'+arguments[i]] = true; 149 } 150 } 151 }; 152 153 /** @ignore */ 154 StringSet.prototype.key = function(x) { 155 return '$$'+x; 156 } 157 158 /** 159 * Returns whether this set contains the given string. 160 * 161 * @param {string} x 162 * @return {boolean} Whether x exits in this set. 163 */ 164 StringSet.prototype.contains = function(x) { 165 return (this._obj.hasOwnProperty(this.key(x)) && 166 this._obj[this.key(x)] === true); 167 }; 168 169 /** 170 * Adds the given string to the set if it is not already in it. 171 * @param {string} x 172 */ 173 StringSet.prototype.add = function(x) { 174 this._obj[this.key(x)] = true; 175 }; 176 177 /** 178 * Removes the given string from the set if it is contained in it. 179 * @param {string} x The string to remove from the set. 180 */ 181 StringSet.prototype.remove = function(x) { 182 if (this._obj[this.key(x)]) { 183 delete this._obj[this.key(x)]; 184 } 185 }; 186 187 /** 188 * Iterators over the strings in the set, calling the provided 189 * function on each element. 190 * @param {function} f The function to call on each string in the 191 * set. 192 */ 193 StringSet.prototype.forEach = function(f) { 194 var self = this; 195 eachProperty(this._obj, function(name) { 196 var realName = name.substring(2); 197 if (self.contains(realName)) f(realName); 198 }); 199 } 200 201 //---------------------------------------------------------------- 202 // wget/wpost 203 //---------------------------------------------------------------- 204 205 /** 206 * The error thrown by wget and wpost if there's a problem retrieveing the requested URL. 207 * 208 * @constructor 209 * @param {string} message A status message describing why the action failed. 210 * @param {HttpResponse} info Additional info, including headers and received data, if any. 211 */ 212 function HttpRequestError(message, info) { 213 this.message = message; 214 this.info = info; 215 this.name = "HttpRequestError"; 216 } 217 HttpRequestError.prototype = new Error(); 218 219 /** 220 * @class Description of an object containing the properties of the HTTP response returned by <code>wget</code> and <code>wpost</code> 221 * 222 * @name HttpResponse 223 */ 224 225 /** 226 * The status code of the server's response, or an internal status code if connecting to the server failed. 227 * @name status 228 * @type number 229 * @memberOf HttpResponse 230 */ 231 /** 232 * An explanation of the status code if it is an internal code (that is, less than 0) 233 * @name statusInfo 234 * @type string 235 * @memberOf HttpResponse 236 */ 237 /** 238 * The data returned by the server, if any. 239 * @name data 240 * @type string 241 * @memberOf HttpResponse 242 */ 243 /** 244 * The content type returned by the server, if any. 245 * @name contentType 246 * @type string 247 * @memberOf HttpResponse 248 */ 249 /** 250 * An object containing property names and values corresponding to the headers returned by the server. 251 * @name headers 252 * @type object 253 * @memberOf HttpResponse 254 */ 255 256 function _paramObjectToParamArray(params, enc) { 257 var pa = []; 258 eachProperty(params, function(k, v) { 259 pa.push(enc ? encodeURIComponent(k.toString()) : k.toString()); 260 pa.push(enc ? encodeURIComponent(v.toString()) : v.toString()); 261 }); 262 return pa; 263 } 264 265 266 /** 267 * Fetches the text of a URL and returns it as a string. 268 * 269 * @example 270 g = wget("google.com"); 271 page.setMode("plain"); 272 print(raw(g)); 273 * 274 * @param {string} url The name of the url to retreive. If the 275 * transport is not specified, HTTP is assumed. 276 * @param {object} [params] Optional parameters to include with the 277 * GET, as a dictionary of {name: value} entries. 278 * @param {object} [options] Optional object with three optional 279 * properties:<ul> 280 * <li><strong>headers:</strong> HTTP request headers to send 281 * with the GET, as a dictionary of {name: value} entries.</li> 282 * <li><strong>followRedirects:</strong> A boolean indicating 283 * whether to follow redirect headers returned by the server. 284 * Defaults to <code>true</code>.</li> 285 * <li><strong>complete:</strong> If <code>true</code>, wget returns 286 * an object containing all information about the response, not just 287 * its contents. Otherwise, wget throws a HttpRequestError if it 288 * encounters an error GETing. 289 * </ul> 290 * @return {string} The full text of the url's content. 291 * @return {HttpResponse} The "complete" response, returned if the "complete" parameter is <code>true</code>. 292 */ 293 function wget(url, params, options) { 294 if (!url.match(/^\w+\:\/\//)) { 295 url = "http://" + url; 296 } 297 var pa = _paramObjectToParamArray(params, false); 298 var ha = _paramObjectToParamArray(options ? options.headers : undefined, false); 299 var followRedir = (options === true ? true : (options && options.followRedirects === false ? false : true)); 300 var ret = appjet._native.simplehttpclient_get(url, pa, ha, followRedir); 301 if ((options === true) || (options && options.complete)) 302 return ret; 303 if (ret.status >= 200 && ret.status < 300) { 304 return ret.data; 305 } else { 306 throw new HttpRequestError(ret.statusInfo || ret.status, ret); 307 } 308 } 309 310 /** 311 * Simple way to POST data to a URL and get back the response. Values of params will 312 * automatically be escaped. 313 * 314 * @example 315 result = wpost("example.com", {id: 25, value: "here is the post value"}); 316 * 317 * @param {string} url The url to POST to. 318 * @param {object} [params] Optional parameters to include with the 319 * POST, as a dictionary of {name: value} entries. 320 * @param {object} [options] Optional object with three optional 321 * properties:<ul> 322 * <li><strong>headers:</strong> HTTP request headers to send 323 * with the POST, as a dictionary of {name: value} entries.</li> 324 * <li><strong>followRedirects:</strong> A boolean indicating 325 * whether to follow redirect headers returned by the server.</li> 326 * <li><strong>complete:</strong> If <code>true</code>, wpost returns 327 * an object containing all information about the response, not just 328 * its contents. Otherwise, wpost throws a HttpRequestError if it 329 * encounters an error POSTing. 330 * </ul> 331 * @return {string} The full text returned from the server POSTed to. 332 * @return {HttpResponse} The complete response, returned if the "complete" parameter is <code>true</code>. 333 */ 334 function wpost(url, params, options) { 335 if (!url.match(/^\w+\:\/\//)) { 336 url = "http://" + url; 337 } 338 var pa = _paramObjectToParamArray(params, true); 339 var ha = _paramObjectToParamArray(options ? options.headers : undefined, false); 340 var followRedir = (options === true ? true : (options && options.followRedirects === false ? false : true)); 341 var ret = appjet._native.simplehttpclient_post(url, pa, ha, followRedir); 342 if ((options === true) || (options && options.complete)) 343 return ret; 344 if (ret.status >= 200 && ret.status < 300) { 345 return ret.data; 346 } else { 347 throw new HttpRequestError(ret.statusInfo || ret.status, ret); 348 } 349 } 350 351 /** 352 * Simple way to send an email to a single recipient. Emails will have a 353 * "from" address of <code>noreply@{appjet.appName}.{appjet.mainDomain}</code>. 354 * 355 * Sending is limited to 100 emails per developer account per day. However, 356 * emails sent to the address on file for the app's owner are not counted 357 * toward this limit. 358 * 359 * @example 360 result = sendEmail("noone@example.com", "Test Subject", 361 "Greetings!", {"Reply-To": "sender@example.com"}); 362 * 363 * @param {string} toAddress The one email address to send a message to. 364 * @param {string} subject The message subject. 365 * @param {string} body The message body. 366 * @param {object} [headers] Optional headers to include in the 367 * message, as a dictionary of {name: value} entries. 368 */ 369 function sendEmail(toAddress, subject, body, headers) { 370 var pa = _paramObjectToParamArray(headers, false); 371 var ret = appjet._native.email_sendEmail(toAddress, subject, body, pa); 372 if (ret != "") 373 throw new Error(ret); 374 } 375 376