1 /** @fileOverview Utilities that just get grafted onto the puredom namespace. */ 2 3 puredom.extend(puredom, /** @lends puredom */ { 4 5 /** When called as a function, <code>puredom.text()</code> acts as an alias of {@link puredom.text.filter}. 6 * @namespace A collection of text utilities and a way to apply them as filters. 7 * @function 8 * @name puredom.text 9 * @param {String} str The string to modify 10 * @param {String|Array} A pipe-separated String or Array, where each value is a filter. Arguments to a filter can be passed using a colon followed by a CSV of the arguments. 11 * @returns {String} The modified string 12 */ 13 text : (function() { 14 /** @exports base as puredom.text */ 15 16 /** @public A collection of text utilities and a way to apply them as filters. <br /> 17 * <strong>Note:</strong> puredom.text() is a convenient shortcut for this method. 18 * @param {String} str The string to modify 19 * @param {String|Array} A pipe-separated String or Array, where each value is a filter. Arguments to a filter can be passed using a colon followed by a CSV of the arguments. 20 * @returns {String} The modified string 21 * @function 22 */ 23 var base = function(){ 24 return base.filter.apply(this, arguments); 25 }, 26 regexes = { 27 htmlEntities : /[&<>"]/gim, 28 ucWords : /(^|\s)[a-z]/gim, 29 ucFirst : /^[a-z]/gim, 30 nl2br : /\r?\n/g, 31 numbersOnly : /[^0-9.\-]/gim, 32 trim : /^\s*?(.*?)\s*?$/gim 33 }; 34 35 /** Modify a string using a filter to apply any functions available in {@link puredom#text}. 36 * @param {String} str The string to modify 37 * @param {String|Array} filters A bar-separated string or {Array}, where each value is a filter. Arguments to a filter can be passed using a colon followed by a CSV of the arguments. 38 * @returns {String} The modified string 39 * @example 40 * puredom.text.filter(" <hi there!> ", 'trim|ucWords|htmlEntities') === "<Hi There!>"; 41 * puredom.text("This string might be too long!", 'truncate:10,byWord') === "This string..."; 42 */ 43 base.filter = function(str, filters) { 44 var x, filter, ind, args, i; 45 if (puredom.typeOf(filters)!=='array') { 46 filters = ((filters||'') + '').split('|'); 47 } 48 if (arguments.length>2) { 49 for (x=2; x<arguments.length; x++) { 50 if (puredom.typeOf(arguments[x])==='array') { 51 filters = filters.concat(arguments[x]); 52 } 53 else { 54 filters.push(arguments[x]); 55 } 56 } 57 } 58 for (x=0; x<filters.length; x++) { 59 filter = filters[x]; 60 args = [str]; 61 ind = filter.indexOf(':'); 62 if (ind>-1) { 63 filter = filter.substring(0, ind); 64 args = args.concat(filters[x].substring(ind+1).split(',')); 65 } 66 for (i in base) { 67 if ((i+'').toLowerCase()===filter.toLowerCase()) { 68 str = base[i].apply(base, args); 69 break; 70 } 71 } 72 } 73 return str; 74 }; 75 76 /** URL-encode a string. (using encodeURIComponent) 77 * @param {String} str The string to modify 78 * @returns {String} The modified string 79 */ 80 base.escape = function(str) { 81 return encodeURIComponent(str); 82 }; 83 84 /** URL-decode a string. (using decodeURIComponent) 85 * @public 86 * @param {String} str The string to modify 87 * @returns {String} The modified string 88 */ 89 base.unescape = function(str) { 90 return decodeURIComponent(str); 91 }; 92 93 /** Convert special characters to their HTML-encoded equivalents. 94 * @param {String} str The string to modify 95 * @returns {String} The modified string 96 */ 97 base.htmlEntities = function(str) { 98 var map = { 99 '&' : '&', 100 '<' : '<', 101 '>' : '>', 102 '"' : '"' 103 }; 104 return (str+'').replace(regexes.htmlEntities, function(s) { 105 return map[s]; 106 }); 107 }; 108 109 /** Convert the first character of each word to uppercase. 110 * @param {String} str The string to modify 111 * @returns {String} The modified string 112 */ 113 base.ucWords = function(str) { 114 return (str+'').toLowerCase().replace(regexes.ucWords, function(s) { 115 return s.toUpperCase(); 116 }); 117 }; 118 119 /** Convert the first character of the first word to uppercase. 120 * @param {String} str The string to modify 121 * @returns {String} The modified string 122 */ 123 base.ucFirst = function(str) { 124 return (str+'').toLowerCase().replace(regexes.ucFirst, function(s) { 125 return s.toUpperCase(); 126 }); 127 }; 128 129 /** Convert newline characters to HTML <br /> elements. 130 * @param {String} str The string to modify 131 * @returns {String} The modified string 132 */ 133 base.nl2br = function(str) { 134 return (str+'').replace(regexes.nl2br, '<br />'); 135 }; 136 137 /** Strip all non-numeric characters from a string. 138 * @param {String} str The string to modify 139 * @returns {String} The modified string 140 */ 141 base.numbersOnly = function(str) { 142 return (str+'').replace(regexes.numbersOnly, ''); 143 }; 144 145 /** Truncate a string, optionally on word boundaries. <br /> 146 * Optionally adds a textual truncation indicator (eg: "..."). 147 * @param {String} str The string to truncate 148 * @param {Number} [maxLen=80] Maximum string length, in characters. 149 * @param {Boolean|String} [byWord=false] Don't truncate in the middle of words. Resultant string may be shorter if set to true. 150 * @param {String} [indicatorChars="..."] Custom indicator characters if truncation occurs. Defaults to "...". 151 * @returns {String} The truncated string 152 */ 153 base.truncate = function(str, maxLen, byWord, indicatorChars) { 154 var trimmed = false, 155 origStr = str+''; 156 str = origStr; 157 maxLen = parseInt(maxLen,10) || 80; 158 byWord = byWord===true || byWord==='true' || byWord==='byWord'; 159 indicatorChars = indicatorChars || '...'; 160 if (str.length>maxLen) { 161 if (byWord) { 162 str = str.substring(0, maxLen); 163 if (!origStr.charAt(maxLen).match(/\s/)) { 164 str = str.replace(/\s[^\s]*$/,''); 165 } 166 } 167 else { 168 str = str.substring(0, maxLen-indicatorChars.length); 169 } 170 trimmed = true; 171 } 172 if (trimmed) { 173 str += indicatorChars; 174 } 175 return str; 176 }; 177 178 /** Fast JS trim implementation across all browsers. <br /> 179 * <em>Note: Research credit goes to http://blog.stevenlevithan.com/archives/faster-trim-javascript</em> 180 * @param {String} str The string to modify 181 * @returns {String} The modified string 182 */ 183 base.trim = function(str) { 184 //return str.replace(regexes.trim, '$1'); 185 var ws = /\s/, i; 186 str = str.replace(/^\s\s*/, ''); 187 i = str.length; 188 while (ws.test(str.charAt(--i))); 189 return str.slice(0, i + 1); 190 }; 191 192 193 /** Default/fallback text. <br /> 194 * Used by templates to provide fallback values for empty fields. 195 * @param {String} str The string to modify 196 * @param {String} text Default text if str is empty. 197 * @returns {String} The modified string 198 */ 199 base['default'] = function(str, text) { 200 str = base.trim(str); 201 return str ? str : text; 202 }; 203 204 205 /** Format a date using whatever i18n module is registered with puredom. <br /> 206 * <em><strong>Note:</strong> Requires a conversion function to be registered as puredom.i18n() in order to convert dates.</em> 207 * @requires puredom.i18n 208 * @param {String} str The string to modify 209 * @param {String} [type=date] A date type to pass to i18n. Defaults to "date". 210 * @returns {String} The formatted date string 211 */ 212 base.dateformat = function(str, type) { 213 var i18n = puredom.i18n, 214 d = puredom.date, 215 date; 216 if (d && d.create) { 217 date = d.create(str); 218 } 219 if (!date || (date+'').indexOf('Invalid')>-1) { 220 date = new Date(str); 221 if (!date || (date+'').indexOf('Invalid')>-1) { 222 date = new Date(); 223 date.setTime(Math.round(str)); 224 } 225 } 226 if (type && type.indexOf('%')>-1) { 227 if (d && d.format) { 228 str = d.format(date, type); 229 } 230 } 231 else if (i18n) { 232 str = i18n(date, null, null, { 233 datetype : type || 'date' 234 }) || (date+''); 235 } 236 return str; 237 }; 238 239 240 return base; 241 }()), 242 243 244 245 /** Convert an object to a sequence of URL-encoded key-value parameters. 246 * This function is the same as {@link puredom.querystring.stringify}, except that 247 * it prepends a '?' to the result by default. (ie: startDelimiter is '?' by default) 248 * @name puredom.parameterize 249 * @param {Object} obj The object to serialize 250 * @param config Configuration overrides. See {@link puredom.querystring.stringify} 251 * @see puredom.querystring.stringify 252 * @deprecated 253 * @private 254 * @returns {String} The generated querystring 255 */ 256 parameterize : function(obj, customConfig) { 257 var t = [], 258 key, value, x, type, 259 config = puredom.extend({ 260 delimiter : '&', 261 startDelimiter : '?', 262 assignment : '=', 263 typeHandlers : null 264 }, customConfig); 265 266 for (key in obj) { 267 if (obj.hasOwnProperty(key)) { 268 value = obj[key]; 269 type = this.typeOf(value); 270 if (config.typeHandlers && config.typeHandlers.hasOwnProperty(type)) { 271 t.push( config.delimiter + encodeURIComponent(key) + "=" + encodeURIComponent(config.typeHandlers[type](value)) ); 272 } 273 else if (type==='array' && config.disableArrayParams!==true) { 274 for (x=0; x<value.length; x++) { 275 t.push( config.delimiter + encodeURIComponent(key) + "[]=" + encodeURIComponent(value[x]) ); 276 } 277 } 278 else { 279 switch (type) { 280 case 'boolean': 281 value = value ? 'true' : 'false'; 282 break; 283 case 'null': 284 case 'undefined': 285 value = ''; 286 break; 287 case 'object': 288 if (config.useJsonForObjects!==false) { 289 // nested objects get serialized as JSON by default: 290 value = this.json(value); 291 } 292 else { 293 // alternatively, they can be serialized by double-encoding: 294 value = this.parameterize(value); 295 } 296 break; 297 } 298 t.push(encodeURIComponent(key) + "=" + encodeURIComponent(value)); 299 } 300 } 301 } 302 t = t.join(config.delimiter || '&'); 303 t = (config.startDelimiter || '') + t; 304 return t; 305 }, 306 307 308 309 /** @namespace Handles querystring encoding and decoding. 310 * @name puredom.querystring 311 */ 312 querystring : { 313 /** @lends puredom.querystring */ 314 315 /** Parse a querystring and return an {Object} with the key-value pairs as its properties. 316 * <em>Note: Preceeding '?' and '&' characters will be stripped. Empty parameters will be returned as empty strings.</em> 317 * @param {String} querystring The querystring to parse. 318 * @returns {Object} The key-value parameters as an object. 319 */ 320 parse : function(str) { 321 var parts, i, j, p={}; 322 if (str.substring(0,1)==='?') { 323 str = str.substring(1); 324 } 325 parts = str.split('&'); 326 for (i=0; i<parts.length; i++) { 327 if (parts[i]) { 328 j = parts[i].indexOf('='); 329 p[decodeURIComponent(parts[i].substring(0,j))] = j<0 ? '' : decodeURIComponent(parts[i].substring(j+1)); 330 } 331 } 332 return p; 333 }, 334 335 /** Convert an object into a querystring, optionally with custom separator/delimiter characters. 336 * <em>Note: Nested objects are serialized as double-encoded querystring parameters by default. To use JSON for nested objects, set the "useJsonForObjects" flag to true.</em> 337 * Available options: 338 * {Boolean} useJsonForObjects Use JSON to serialize nested objects? (uses double-encoding by default) 339 * {Boolean} disableArrayParams Disable PHP-style "array parameters? ex: p[]=foo&p[]=bar 340 * {Object} typeHandlers Specify custom serializers for each data type by setting type:handler. Handlers accept the original data and return the serialized parameter value, *not* URL-encoded. 341 * {String} assignment The key-value separator. Defaults to "=". 342 * {String} delimiter The group separator. Defaults to "&". 343 * {String} startDelimiter A character to insert at the beginning of the string. Defaults to none. 344 * @param {Object} parameters A key-value map of parameters to serialize. 345 * @param {Object} [options] A hashmap of configuration options. 346 */ 347 stringify : function(parameters, options) { 348 options = puredom.extend({ startDelimiter:'' }, options || {}); 349 return puredom.parameterize(parameters, options); 350 }, 351 build : function(){return puredom.querystring.stringify.apply(puredom.querystring,arguments);} 352 }, 353 354 355 356 /** @namespace Handles storage and retrieval of cookies. 357 * @name puredom.cookies 358 */ 359 cookies : (function(){ 360 var cache = {}; 361 362 return /** @lends puredom.cookies */ { 363 364 /** Set a cookie with name *key* to value *value* 365 * @exports set as puredom.cookies.set 366 * @param {String} key The key for storage 367 * @param {String} value A value to store 368 * @param {Number} days The cookie lifetime in number of days. 369 */ 370 set : function (key, value, days, domain, path, secure) { 371 var expires = '', 372 cookie = '', 373 date; 374 path = typeof path==='string' ? path : ''; 375 if (days) { 376 date = new Date(); 377 date.setTime(date.getTime() + days*24*60*60*1000); 378 expires = "; expires="+date.toGMTString(); 379 } 380 if(cache.hasOwnProperty(key) && cache[key].expires) { 381 expires = "; expires="+cache[key].expires.toGMTString(); 382 } 383 cookie = key + "=" + encodeURIComponent(value) + expires + "; path=/"+path.replace(/^\//,''); 384 if (typeof domain==='string' && domain.length>0) { 385 cookie += '; domain=' + domain.replace(/[\;\,]/,''); 386 } 387 if (secure===true) { 388 cookie += '; secure'; 389 } 390 //puredom.log('puredom.cookies.set() :: ' + cookie); 391 document.cookie = cookie; 392 cache[key] = { 393 value : value, 394 expires : date 395 }; 396 }, 397 398 /** Get a cookie. Pulls values from cache when possible. 399 * @exports get as puredom.cookies.get 400 * @param {String} key The key to lookup 401 * @param {Boolean} [useCached=true] Use cached value if present 402 * @returns {String} value The value, or <code>null</code> if the lookup failed. 403 */ 404 get : function (key, useCached) { 405 if(cache.hasOwnProperty(key) && useCached!==true) { 406 return cache[key].value; 407 } 408 var c, i, ca = document.cookie.split(';'); 409 for (i=0; i<ca.length; i++) { 410 c = ca[i].replace(/^\s+/gim,''); 411 if (c.indexOf(key+"=")===0) { 412 return decodeURIComponent(c.substring(key.length+1,c.length)); 413 } 414 } 415 return null; 416 }, 417 /** Remove a cookie and any cached values 418 * @param {String} key The key to remove 419 */ 420 remove : function (key) { 421 this.set(key, "", -1); 422 delete cache[key]; 423 }, 424 /** Remove all cookies and cached values */ 425 purge : function () { 426 for (var x in cache) { 427 if(cache.hasOwnProperty(x)) { 428 this.remove(x); 429 delete cache[x]; 430 } 431 } 432 }, 433 /** Alias of {@link puredom.cookies.get} 434 * @see puredom.cookies.get 435 * @private 436 */ 437 read : function() { 438 return this.get.apply(this,arguments); 439 }, 440 /** Alias of {@link puredom.cookies.set} 441 * @see puredom.cookies.set 442 * @private 443 */ 444 write : function() { 445 return this.set.apply(this,arguments); 446 } 447 }; 448 }()), 449 450 451 /** @ignore */ 452 Cache : (function() { 453 /** @class In-memeory cache class with a twist! <br /> 454 * Set and get work like a normal cache. 455 * Creates a new Cache instance. 456 * @name puredom.Cache 457 */ 458 function Cache() { 459 if (this.constructor!==arguments.callee && this.constructor!==Cache) { 460 return new Cache(); 461 } 462 this.data = {}; 463 } 464 465 puredom.extend(Cache.prototype, /** @lends puredom.Cache# */ { 466 467 /** The default *type* used for namespacing keys is "_default" */ 468 defaultType : '_default', 469 470 /** Purge all entries from the cache */ 471 purge : function() { 472 this.data = {}; 473 }, 474 475 /** Get a cached value with optional type. 476 * @param {String} [type] A type prefix. 477 * @param {String|Number} id The cache entry ID 478 * @param {Function} callback A callback, gets passed the cached value once retrieved. 479 */ 480 get : function(type, id, cb) { 481 var d; 482 if (arguments.length===2) { 483 id = type; 484 cb = id; 485 type = null; 486 } 487 type = (type || this.defaultType)+''; 488 id = id+''; 489 d = this.data.hasOwnProperty(type) && this.data[type][id] || false; 490 if (cb) { 491 if (d) { 492 cb(d); 493 } 494 return !!d; 495 } 496 return d; 497 }, 498 499 /** Get a cached value with optional type. 500 * @param {String} [type] A type prefix. 501 * @param {String|Number} id The cache entry ID 502 * @param value Any value to cache. 503 */ 504 set : function(type, id, val) { 505 if (arguments.length===2) { 506 id = type; 507 val = id; 508 type = null; 509 } 510 type = (type || this.defaultType)+''; 511 id = id+''; 512 if (!this.data[type]) { 513 this.data[type] = {}; 514 } 515 this.data[type][id] = val; 516 }, 517 518 /** Proxy a callback function for automatically caching asynchronous responses. 519 * @param {String} [type] A type prefix. 520 * @param {String|Number} id The cache entry ID 521 * @param {Function} callback The callback function to inject c 522 * @param {Number} paramIndex Which callback parameter to cache (0-based). 523 * @returns {Function} The proxied callback function, with the cache set injected. 524 */ 525 proxySet : function(type, id, callback, paramIndex) { 526 var self = this; //, cb; 527 //cb = function() { 528 return function() { 529 self.set(type, id, arguments[paramIndex || 0]); 530 if (callback) { 531 callback.apply(callback, arguments); 532 } 533 //self = cb = type = id = callback = paramIndex = null; 534 }; 535 //return cb; 536 }, 537 538 /** Iterate over all the cache entries. 539 * @param {Function} iterator Gets passed each entry. 540 */ 541 each : function(iterator) { 542 return puredom.foreach(this.data, iterator); 543 } 544 }); 545 return Cache; 546 }()), 547 548 549 550 /** @namespace Parse and generate JSON. 551 * When called as a function, <code>puredom.json()</code> automatically converts between JSON-Strings and Objects. 552 * @function 553 * @name puredom.json 554 * @param {String|Object|Array} what If a String is passed, it is parsed as JSON. Otherwise, returns JSON-encoded value of <code>what</code>. 555 * @returns {String|Object|Array} jsonStringOrJsonResult 556 */ 557 json : (function() { 558 /** @exports json as puredom.json */ 559 560 /** @private */ 561 var json = function(what) { 562 if (puredom.typeOf(what)==="string") { 563 return json.parse(what); 564 } 565 return json.stringify(what); 566 }; 567 568 /** Serialize a JavaScript object structure to a JSON string.<br /> 569 * <em>Note: Circular references cause this function to fail.</em> 570 * @param what Any object of any type. 571 * @returns {String} The JSON-encoded string 572 */ 573 json.stringify = function(what) { 574 var result; 575 try { 576 result = JSON.stringify(what); 577 }catch(err) { 578 puredom.log("puredom.json:: Stringify failed: " + err + " | " + what); 579 } 580 return result; 581 }; 582 583 /** Parse JSON from a {String} and return the resulting object. 584 * @param {String} json A string containing JSON. 585 * @returns {Object|Array|String|Number} jsonResult 586 * @example 587 * var obj = puredom.json.parse('{"items":[{"title":"Example"}]}'); 588 */ 589 json.parse = function(what) { 590 var result; 591 if (typeof what==='string' && what.length>0) { 592 try { 593 result = JSON.parse(what); 594 }catch(err) { 595 puredom.log("puredom.json:: Parse failed: " + err + " | " + what); 596 } 597 } 598 return result; 599 }; 600 601 /** Alias of {@link puredom.json.stringify} 602 * @function 603 * @deprecated 604 * @private 605 */ 606 json.serialize = json.stringify; 607 608 /** Alias of {@link puredom.json.parse} 609 * @function 610 * @deprecated 611 * @private 612 */ 613 json.unserialize = json.parse; 614 615 return json; 616 }()), 617 618 619 620 /** @namespace Parse and generate XML. 621 * @name puredom.xml 622 */ 623 xml : /** @lends puredom.xml */ { 624 625 /** Parse XML from a string and return the resulting {Document}. 626 * @param {String} xmlString The XML to parse 627 * @returns {Document} The XML document. 628 * @example 629 * var doc = puredom.xml.parse('<items><item><title>Example</title></item></items>'); 630 */ 631 parse : function(xmlString) { 632 var xmlDoc; 633 if (window.DOMParser) { 634 xmlDoc = new window.DOMParser().parseFromString(xmlString, "text/xml"); 635 } 636 else { 637 // Internet Explorer 638 xmlDoc = new window.ActiveXObject("Microsoft.XMLDOM"); 639 xmlDoc.async = "false"; 640 xmlDoc.loadXML(xmlString); 641 } 642 return xmlDoc; 643 } 644 } 645 646 });