1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. 5 * 6 * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at: https://www.zimbra.com/license 9 * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15 10 * have been added to cover use of software over a computer network and provide for limited attribution 11 * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B. 12 * 13 * Software distributed under the License is distributed on an "AS IS" basis, 14 * WITHOUT WARRANTY OF ANY KIND, either express or implied. 15 * See the License for the specific language governing rights and limitations under the License. 16 * The Original Code is Zimbra Open Source Web Client. 17 * The Initial Developer of the Original Code is Zimbra, Inc. All rights to the Original Code were 18 * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015. 19 * 20 * All portions of the code are Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * AjxUtil - static class with some utility methods. This is where to 26 * put things when no other class wants them. 27 * 28 * 12/3/2004 At this point, it only needs AjxEnv to be loaded. 29 * 30 * @private 31 */ 32 AjxUtil = function() { 33 }; 34 35 AjxUtil.FLOAT_RE = /^[+\-]?((\d+(\.\d*)?)|((\d*\.)?\d+))([eE][+\-]?\d+)?$/; 36 AjxUtil.NOTFLOAT_RE = /[^\d\.]/; 37 AjxUtil.NOTINT_RE = /[^0-9]+/; 38 AjxUtil.LIFETIME_FIELD = /^([0-9])+([dhms]|ms)?$/; 39 AjxUtil.INT_RE = /^\-?(0|[1-9]\d*)$/; 40 41 AjxUtil.isSpecified = function(aThing) { return ((aThing !== void 0) && (aThing !== null)); }; 42 AjxUtil.isUndefined = function(aThing) { return (aThing === void 0); }; 43 AjxUtil.isNull = function(aThing) { return (aThing === null); }; 44 AjxUtil.isBoolean = function(aThing) { return (typeof(aThing) === 'boolean'); }; 45 AjxUtil.isString = function(aThing) { return (typeof(aThing) === 'string'); }; 46 AjxUtil.isNumber = function(aThing) { return (typeof(aThing) === 'number'); }; 47 AjxUtil.isObject = function(aThing) { return ((typeof(aThing) === 'object') && (aThing !== null)); }; 48 AjxUtil.isArray = function(aThing) { return AjxUtil.isInstance(aThing, Array); }; 49 AjxUtil.isArrayLike = function(aThing) { return typeof aThing !== 'string' && typeof aThing.length === 'number'; }; 50 AjxUtil.isFunction = function(aThing) { return (typeof(aThing) === 'function'); }; 51 AjxUtil.isDate = function(aThing) { return AjxUtil.isInstance(aThing, Date); }; 52 AjxUtil.isLifeTime = function(aThing) { return AjxUtil.LIFETIME_FIELD.test(aThing); }; 53 AjxUtil.isNumeric = function(aThing) { return (!isNaN(parseFloat(aThing)) && AjxUtil.FLOAT_RE.test(aThing) && !AjxUtil.NOTFLOAT_RE.test(aThing)); }; 54 AjxUtil.isInt = function(aThing) { return AjxUtil.INT_RE.test(aThing); }; 55 AjxUtil.isPositiveInt = function(aThing) { return AjxUtil.isInt(aThing) && parseInt(aThing, 10) > 0; }; //note - assume 0 is not positive 56 AjxUtil.isLong = AjxUtil.isInt; 57 AjxUtil.isNonNegativeLong = function(aThing) { return AjxUtil.isLong(aThing) && parseInt(aThing, 10) >= 0; }; 58 AjxUtil.isEmpty = function(aThing) { return ( AjxUtil.isNull(aThing) || AjxUtil.isUndefined(aThing) || (aThing === "") || (AjxUtil.isArray(aThing) && (aThing.length==0))); }; 59 // REVISIT: Should do more precise checking. However, there are names in 60 // common use that do not follow the RFC patterns (e.g. domain 61 // names that start with digits). 62 AjxUtil.IPv4_ADDRESS_RE = /^\d{1,3}(\.\d{1,3}){3}(\.\d{1,3}\.\d{1,3})?$/; 63 AjxUtil.IPv4_ADDRESS_WITH_PORT_RE = /^\d{1,3}(\.\d{1,3}){3}(\.\d{1,3}\.\d{1,3})?:\d{1,5}$/; 64 AjxUtil.IPv6_ADDRESS_RE = /^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|(?:%[-\w.~]+)?$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})(?:%[-\w.~]+)?$/i; 65 AjxUtil.IPv6_ADDRESS_WITH_PORT_RE = new RegExp(AjxUtil.IPv6_ADDRESS_RE.source.replace('^', '^\\[').replace('$', '\\]:\\d{1,5}$'), 'i'); 66 AjxUtil.SUBNET_RE = /^\d{1,3}(\.\d{1,3}){3}(\.\d{1,3}\.\d{1,3})?\/\d{1,2}$/; 67 AjxUtil.DOMAIN_NAME_SHORT_RE = /^[A-Za-z0-9\-]{2,}$/; 68 AjxUtil.DOMAIN_NAME_FULL_RE = /^[A-Za-z0-9\-]{1,}(\.[A-Za-z0-9\-]{2,}){1,}$/; 69 AjxUtil.HOST_NAME_RE = /^[A-Za-z0-9\-]{2,}(\.[A-Za-z0-9\-]{1,})*(\.[A-Za-z0-9\-]{2,})*$/; 70 AjxUtil.HOST_NAME_WITH_PORT_RE = /^[A-Za-z0-9\-]{2,}(\.[A-Za-z0-9\-]{2,})*:([0-9])+$/; 71 AjxUtil.EMAIL_SHORT_RE = /^[^@\s]+$/; 72 AjxUtil.EMAIL_FULL_RE = /^[^@\s]+@[A-Za-z0-9\-]{2,}(\.[A-Za-z0-9\-]{2,})+$/; 73 AjxUtil.FULL_URL_RE = /^[A-Za-z0-9]{2,}:\/\/[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)*(:([0-9])+)?(\/[\w\.\|\^\*\[\]\{\}\(\)\-<>~,'#_;@:!%]+)*(\/)?(\?[\w\.\|\^\*\+\[\]\{\}\(\)\-<>~,'#_;@:!%&=]*)?$/; 74 AjxUtil.IP_FULL_URL_RE = /^[A-Za-z0-9]{2,}:\/\/\d{1,3}(\.\d{1,3}){3}(\.\d{1,3}\.\d{1,3})?(:([0-9])+)?(\/[\w\.\|\^\*\[\]\{\}\(\)\-<>~,'#_;@:!%]+)*(\/)?(\?[\w\.\|\^\*\+\[\]\{\}\(\)\-<>~,'#_;@:!%&=]*)?$/; 75 AjxUtil.SHORT_URL_RE = /^[A-Za-z0-9]{2,}:\/\/[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)*(:([0-9])+)?$/; 76 AjxUtil.IP_SHORT_URL_RE = /^[A-Za-z0-9]{2,}:\/\/\d{1,3}(\.\d{1,3}){3}(\.\d{1,3}\.\d{1,3})?(:([0-9])+)?$/; 77 78 AjxUtil.isHostName = function(s) { return AjxUtil.HOST_NAME_RE.test(s); }; 79 AjxUtil.isDomainName = 80 function(s, shortMatch) { 81 return shortMatch 82 ? AjxUtil.DOMAIN_NAME_SHORT_RE.test(s) 83 : AjxUtil.DOMAIN_NAME_FULL_RE.test(s); 84 }; 85 86 AjxUtil.isEmailAddress = 87 function(s, nameOnly) { 88 return nameOnly 89 ? AjxUtil.EMAIL_SHORT_RE.test(s) 90 : AjxUtil.EMAIL_FULL_RE.test(s); 91 }; 92 93 AjxUtil.isValidEmailNonReg = 94 function(s) { 95 return ((s.indexOf ("@") > 0) && (s.lastIndexOf ("@") == s.indexOf ("@")) && (s.indexOf (".@") < 0)); 96 }; 97 98 /** 99 * Return true if the given object is a plain hash. 100 * 101 * @param aThing The object for testing. 102 */ 103 AjxUtil.isHash = 104 function(aThing) { 105 // Note: can't just look at prototype since that fails cross-window. 106 // See http://stackoverflow.com/questions/10741618/how-to-check-if-argument-is-an-object-and-not-an-array, esp the part with isPlainObject() 107 var str = aThing && aThing.toString ? aThing.toString() : Object.prototype.toString.call(aThing); 108 return AjxUtil.isObject(aThing) && str === '[object Object]'; 109 }; 110 111 AjxUtil.SIZE_GIGABYTES = "GB"; 112 AjxUtil.SIZE_MEGABYTES = "MB"; 113 AjxUtil.SIZE_KILOBYTES = "KB"; 114 AjxUtil.SIZE_BYTES = "B"; 115 116 /** 117 * Formats a size (in bytes) to the largest whole unit. For example, 118 * AjxUtil.formatSize(302132199) returns "288 MB". 119 * 120 * @param size The size (in bytes) to be formatted. 121 * @param round True to round to nearest integer. Default is true. 122 * @param fractions Number of fractional digits to display, if not rounding. 123 * Trailing zeros after the decimal point are trimmed. 124 */ 125 AjxUtil.formatSize = 126 function(size, round, fractions) { 127 if (round == null) round = true; 128 if (fractions == null) fractions = 20; // max allowed for toFixed is 20 129 130 var formattedUnits = AjxMsg.sizeBytes; 131 var units = AjxMsg.SIZE_BYTES; 132 if (size >= 1073741824) { 133 formattedUnits = AjxMsg.sizeGigaBytes; 134 units = AjxUtil.SIZE_GIGABYTES; 135 } 136 else if (size >= 1048576) { 137 formattedUnits = AjxMsg.sizeMegaBytes; 138 units = AjxUtil.SIZE_MEGABYTES; 139 } 140 else if (size > 1023) { 141 formattedUnits = AjxMsg.sizeKiloBytes; 142 units = AjxUtil.SIZE_KILOBYTES; 143 } 144 145 146 var formattedSize = AjxUtil.formatSizeForUnits(size, units, round, fractions); 147 return AjxMessageFormat.format(AjxMsg.formatSizeAndUnits, [formattedSize, formattedUnits]); 148 }; 149 150 /** 151 * Formats a size (in bytes) to a specific unit. Since the unit size is 152 * known, the unit is not shown in the returned string. For example, 153 * AjxUtil.formatSizeForUnit(302132199, AjxUtil.SIZE_MEGABYTES, false, 2) 154 * returns "288.13". 155 * 156 * @param size The size (in bytes) to be formatted. 157 * @param units The unit of measure. 158 * @param round True to round to nearest integer. Default is true. 159 * @param fractions Number of fractional digits to display, if not rounding. 160 * Trailing zeros after the decimal point are trimmed. 161 */ 162 AjxUtil.formatSizeForUnits = 163 function(size, units, round, fractions) { 164 if (units == null) units = AjxUtil.SIZE_BYTES; 165 if (round == null) round = true; 166 if (fractions == null) fractions = 20; // max allowed for toFixed is 20 167 168 switch (units) { 169 case AjxUtil.SIZE_GIGABYTES: { size /= 1073741824; break; } 170 case AjxUtil.SIZE_MEGABYTES: { size /= 1048576; break; } 171 case AjxUtil.SIZE_KILOBYTES: { size /= 1024; break; } 172 } 173 var dot = I18nMsg.numberSeparatorDecimal !='' ? I18nMsg.numberSeparatorDecimal : '.'; 174 var pattern = I18nMsg.formatNumber.replace(/\..*$/, ""); // Strip off decimal, we'll be adding one anyway 175 pattern = pattern.replace(/,/, ""); // Remove the , 176 if (!round && fractions) { 177 pattern = pattern += dot; 178 for (var i = 0; i < fractions; i++) { 179 pattern += "#"; 180 } 181 } 182 return AjxNumberFormat.format(pattern, size); 183 }; 184 185 /** 186 * Performs the opposite of AjxUtil.formatSize in that this function takes a 187 * formatted size. 188 * 189 * @param units Unit constant: "GB", "MB", "KB", "B". Must be specified 190 * unless the formatted size ends with the size marker, in 191 * which case the size marker in the formattedSize param 192 * overrides this parameter. 193 */ 194 AjxUtil.parseSize = 195 function(formattedSize, units) { 196 // NOTE: Take advantage of fact that parseFloat ignores bad chars 197 // after numbers 198 if (typeof formattedSize != _STRING_) { 199 formattedSize = formattedSize.toString() ; 200 } 201 var size = parseFloat(formattedSize.replace(/^\s*/,"")); 202 203 var marker = /[GMK]?B$/i; 204 var result = marker.exec(formattedSize); 205 if (result) { 206 //alert("units: "+units+", result[0]: '"+result[0]+"'"); 207 units = result[0].toUpperCase(); 208 } 209 210 switch (units) { 211 case AjxUtil.SIZE_GIGABYTES: size *= 1073741824; break; 212 case AjxUtil.SIZE_MEGABYTES: size *= 1048576; break; 213 case AjxUtil.SIZE_KILOBYTES: size *= 1024; break; 214 } 215 216 //alert("AjxUtil#parseSize: formattedSize="+formattedSize+", size="+size); 217 return size; 218 }; 219 220 AjxUtil.isInstance = 221 function(aThing, aClass) { 222 return !!(aThing && aThing.constructor && (aThing.constructor === aClass)); 223 }; 224 225 AjxUtil.assert = 226 function(aCondition, aMessage) { 227 if (!aCondition && AjxUtil.onassert) AjxUtil.onassert(aMessage); 228 }; 229 230 AjxUtil.onassert = 231 function(aMessage) { 232 // Create an exception object and set the message 233 var myException = new Object(); 234 myException.message = aMessage; 235 236 // Compile a stack trace 237 var myStack = new Array(); 238 if (AjxEnv.isIE5_5up) { 239 // On IE, the caller chain is on the arguments stack 240 var myTrace = arguments.callee.caller; 241 var i = 0; // stop at 20 since there might be somehow an infinite loop here. Maybe in case of a recursion. 242 while (myTrace && i++ < 20) { 243 myStack[myStack.length] = myTrace; 244 myTrace = myTrace.caller; 245 } 246 } else { 247 try { 248 var myTrace = arguments.callee.caller; 249 while (myTrace) { 250 myStack[myStack.length] = myTrace; 251 if (myStack.length > 2) break; 252 myTrace = myTrace.caller; 253 } 254 } catch (e) { 255 } 256 } 257 myException.stack = myStack; 258 259 // Alert with the message and a description of the stack 260 var stackString = ''; 261 var MAX_LEN = 170; 262 for (var i = 1; i < myStack.length; i++) { 263 if (i > 1) stackString += '\n'; 264 if (i < 11) { 265 var fs = myStack[i].toString(); 266 if (fs.length > MAX_LEN) { 267 fs = fs.substr(0,MAX_LEN) + '...'; 268 fs = fs.replace(/\n/g, ''); 269 } 270 stackString += i + ': ' + fs; 271 } else { 272 stackString += '(' + (myStack.length - 11) + ' frames follow)'; 273 break; 274 } 275 } 276 alert('assertion:\n\n' + aMessage + '\n\n---- Call Stack ---\n' + stackString); 277 278 // Now throw the exception 279 throw myException; 280 }; 281 282 // IE doesn't define Node type constants 283 AjxUtil.ELEMENT_NODE = 1; 284 AjxUtil.TEXT_NODE = 3; 285 AjxUtil.DOCUMENT_NODE = 9; 286 287 AjxUtil.getInnerText = 288 function(node) { 289 if (AjxEnv.isIE) 290 return node.innerText; 291 292 function f(n) { 293 if (n) { 294 if (n.nodeType == 3 /* TEXT_NODE */) 295 return n.data; 296 if (n.nodeType == 1 /* ELEMENT_NODE */) { 297 if (/^br$/i.test(n.tagName)) 298 return "\r\n"; 299 var str = ""; 300 for (var i = n.firstChild; i; i = i.nextSibling) 301 str += f(i); 302 return str; 303 } 304 } 305 return ""; 306 }; 307 return f(node); 308 }; 309 310 /** 311 * This method returns a proxy for the specified object. This is useful when 312 * you want to modify properties of an object and want to keep those values 313 * separate from the values in the original object. You can then iterate 314 * over the proxy's properties and use the <code>hasOwnProperty</code> 315 * method to determine if the property is a new value in the proxy. 316 * <p> 317 * <strong>Note:</strong> 318 * A reference to the original object is stored in the proxy as the "_object_" 319 * property. 320 * 321 * @param object [object] The object to proxy. 322 * @param level [number] The number of property levels deep to proxy. 323 * Defaults to zero. 324 */ 325 AjxUtil.createProxy = 326 function(object, level) { 327 var proxy; 328 var proxyCtor = function(){}; // bug #6517 (Safari doesnt like 'new Function') 329 proxyCtor.prototype = object; 330 if (object instanceof Array) { 331 proxy = new Array(); 332 var cnt = object.length; 333 for(var ix = 0; ix < cnt; ix++) { 334 proxy[ix] = object[ix]; 335 } 336 } else { 337 proxy = new proxyCtor; 338 } 339 340 if (level) { 341 for (var prop in object) { 342 if (typeof object[prop] == "object" && object[prop] !== null) 343 proxy[prop] = AjxUtil.createProxy(object[prop], level - 1); 344 } 345 } 346 347 proxy._object_ = object; 348 return proxy; 349 }; 350 351 AjxUtil.unProxy = 352 function(proxy) { 353 var object = proxy && proxy._object_; 354 if (object) { 355 for (var prop in proxy) { 356 if (proxy.hasOwnProperty(prop) && prop!="_object_") { 357 object[prop] = proxy[prop]; 358 } 359 } 360 return object; 361 } 362 return null; 363 } 364 365 /** 366 * Returns a copy of a list with empty members removed. 367 * 368 * @param list [array] original list 369 */ 370 AjxUtil.collapseList = 371 function(list) { 372 var newList = []; 373 for (var i = 0; i < list.length; i++) { 374 if (list[i]) { 375 newList.push(list[i]); 376 } 377 } 378 return newList; 379 }; 380 381 AjxUtil.arrayAsHash = 382 function(array, valueOrFunc) { 383 array = AjxUtil.toArray(array); 384 var hash = {}; 385 var func = typeof valueOrFunc == "function" && valueOrFunc; 386 var value = valueOrFunc || true; 387 for (var i = 0; i < array.length; i++) { 388 var key = array[i]; 389 hash[key] = func ? func(key, hash, i, array) : value; 390 } 391 return hash; 392 }; 393 394 AjxUtil.arrayAdd = function(array, member, index) { 395 396 array = array || []; 397 398 if (index == null || index < 0 || index >= array.length) { 399 // index absent or is out of bounds - append object to the end 400 array.push(member); 401 } else { 402 // otherwise, insert object 403 array.splice(index, 0, member); 404 } 405 }; 406 407 AjxUtil.arrayRemove = 408 function(array, member) { 409 if (array) { 410 for (var i = 0; i < array.length; i++) { 411 if (array[i] == member) { 412 array.splice(i, 1); 413 return true; 414 } 415 } 416 } 417 return false; 418 }; 419 420 AjxUtil.indexOf = 421 function(array, object, strict) { 422 if (array) { 423 for (var i = 0; i < array.length; i++) { 424 var item = array[i]; 425 if ((strict && item === object) || (!strict && item == object)) { 426 return i; 427 } 428 } 429 } 430 return -1; 431 }; 432 433 AjxUtil.arrayContains = 434 function(array, object, strict) { 435 return AjxUtil.indexOf(array, object, strict) != -1; 436 }; 437 438 AjxUtil.keys = function(object, acceptFunc) { 439 var keys = []; 440 for (var p in object) { 441 if (acceptFunc && !acceptFunc(p, object)) continue; 442 keys.push(p); 443 } 444 return keys; 445 }; 446 AjxUtil.values = function(object, acceptFunc) { 447 var values = []; 448 for (var p in object) { 449 if (acceptFunc && !acceptFunc(p, object)) continue; 450 values.push(object[p]); 451 } 452 return values; 453 }; 454 455 /** 456 * Generate another hash mapping property values to their names. Each value 457 * should be unique; otherwise the results are undefined. 458 * 459 * @param obj An object, treated as a hash. 460 * @param func [function] An optional function for filtering properties. 461 */ 462 AjxUtil.valueHash = function(obj, acceptFunc) { 463 // don't rely on the value in the object itself 464 var hasown = Object.prototype.hasOwnProperty.bind(obj); 465 466 var r = {}; 467 for (var k in obj) { 468 var v = obj[k]; 469 470 if (!hasown(k) || (acceptFunc && !acceptFunc(k, obj))) 471 continue; 472 r[v] = k; 473 } 474 return r; 475 }; 476 AjxUtil.backMap = AjxUtil.valueHash; 477 478 /** 479 * Call a function with the the items in the given object, which special logic 480 * for handling of arrays. 481 * 482 * @param obj Array or other object 483 * @param func [function] Called with index or key and value. 484 */ 485 AjxUtil.foreach = function(obj, func) { 486 487 if (!func || !obj) { 488 return; 489 } 490 491 if (AjxUtil.isArrayLike(obj)) { 492 var array = obj; 493 494 for (var i = 0; i < array.length; i++) { 495 func(array[i], i); 496 } 497 } 498 else { 499 // don't rely on the value in the object itself 500 var hasown = Object.prototype.hasOwnProperty.bind(obj); 501 502 for (var k in obj) { 503 if (hasown(k)) { 504 func(obj[k], k) 505 } 506 } 507 } 508 }; 509 510 AjxUtil.map = function(array, func) { 511 512 if (!array) { 513 return []; 514 } 515 516 var narray = new Array(array.length); 517 for (var i = 0; i < array.length; i++) { 518 narray[i] = func ? func(array[i], i) : array[i]; 519 } 520 521 return narray; 522 }; 523 524 AjxUtil.uniq = function(array) { 525 526 if (!array) { 527 return []; 528 } 529 530 var object = {}; 531 for (var i = 0; i < array.length; i++) { 532 object[array[i]] = true; 533 } 534 535 return AjxUtil.keys(object); 536 }; 537 538 539 /** 540 * Remove duplicates from the given array, 541 * <strong>in-place</strong>. Stable with regards to ordering. 542 * 543 * Please note that this method is O(n^2) if Array is backed by an 544 * array/vector data structure. 545 * 546 * @param array [array] array to process 547 * @param keyfn [function] used to extract a comparison key from each 548 * list element, default is to compare 549 * elements directly. if the comparison key 550 * is 'undefined', the element is always 551 * retained 552 */ 553 AjxUtil.dedup = function(array, keyfn) { 554 555 if (!array) { 556 return []; 557 } 558 559 if (!keyfn) { 560 keyfn = function(v) { return v; }; 561 } 562 563 var seen = {}; 564 565 for (var i = 0; i < array.length; i++) { 566 var key = keyfn(array[i]); 567 568 if (key !== undefined && seen[key]) { 569 array.splice(i, 1); 570 i -= 1; 571 } 572 573 seen[key] = true; 574 } 575 }; 576 577 578 AjxUtil.concat = function(array1 /* ..., arrayN */) { 579 580 var array = []; 581 for (var i = 0; i < arguments.length; i++) { 582 array.push.apply(array, arguments[i]); 583 } 584 return array; 585 }; 586 587 AjxUtil.union = function(array1 /* ..., arrayN */) { 588 var array = []; 589 return AjxUtil.uniq(array.concat.apply(array, arguments)); 590 }; 591 592 AjxUtil.intersection = function(array1 /* ..., arrayN */) { 593 var array = AjxUtil.concat.apply(this, arguments); 594 var object = AjxUtil.arrayAsHash(array, AjxUtil.__intersection_count); 595 for (var p in object) { 596 if (object[p] == 1) { 597 delete object[p]; 598 } 599 } 600 return AjxUtil.keys(object); 601 }; 602 603 AjxUtil.__intersection_count = function(key, hash, index, array) { 604 return hash[key] != null ? hash[key] + 1 : 1; 605 }; 606 607 AjxUtil.complement = function(array1, array2) { 608 var object1 = AjxUtil.arrayAsHash(array1); 609 var object2 = AjxUtil.arrayAsHash(array2); 610 for (var p in object2) { 611 if (p in object1) { 612 delete object2[p]; 613 } 614 } 615 return AjxUtil.keys(object2); 616 }; 617 618 /** 619 * Returns an array with all the members of the given array for which the filtering function returns true. 620 * 621 * @param {Array} array source array 622 * @param {Function} func filtering function 623 * @param {Object} context scope for filtering function 624 * 625 * @returns {Array} array of members for which the filtering function returns true 626 */ 627 AjxUtil.filter = function(array, func, context) { 628 629 var results = []; 630 if (array == null) { 631 return results; 632 } 633 634 var nativeFilter = Array.prototype.filter; 635 if (nativeFilter && array.filter === nativeFilter) { 636 return array.filter(func, context); 637 } 638 639 AjxUtil.foreach(array, function(value, index) { 640 if (func.call(context, value, index)) { 641 results.push(value); 642 } 643 }); 644 645 return results; 646 }; 647 648 AjxUtil.getFirstElement = function(parent, ename, aname, avalue) { 649 for (var child = parent.firstChild; child; child = child.nextSibling) { 650 if (child.nodeType != AjxUtil.ELEMENT_NODE) continue; 651 if (ename && child.nodeName != ename) continue; 652 if (aname) { 653 var attr = child.getAttributeNode(aname); 654 if (attr.nodeName != aname) continue; 655 if (avalue && attr.nodeValue != avalue) continue; 656 } 657 return child; 658 } 659 return null; 660 }; 661 662 /** 663 * @param params [hash] hash of params: 664 * relative [boolean]* if true, return a relative URL 665 * protocol [string]* protocol (trailing : is optional) 666 * host [string]* server hostname 667 * port [int]* server port 668 * path [string]* URL path 669 * qsReset [boolean]* if true, clear current query string 670 * qsArgs [hash]* set of query string names and values 671 */ 672 AjxUtil.formatUrl = 673 function(params) { 674 params = params || {}; 675 var url = []; 676 var i = 0; 677 if (!params.relative) { 678 var proto = params.protocol || location.protocol; 679 if (proto.indexOf(":") == -1) { 680 proto = proto + ":"; 681 } 682 url[i++] = proto; 683 url[i++] = "//"; 684 url[i++] = params.host || location.hostname; 685 var port = Number(params.port || location.port); 686 if (port && 687 ((proto == ZmSetting.PROTO_HTTP && port != ZmSetting.HTTP_DEFAULT_PORT) || 688 (proto == ZmSetting.PROTO_HTTPS && port != ZmSetting.HTTPS_DEFAULT_PORT))) { 689 url[i++] = ":"; 690 url[i++] = port; 691 } 692 } 693 url[i++] = params.path || location.pathname; 694 var qs = ""; 695 if (params.qsArgs) { 696 qs = AjxStringUtil.queryStringSet(params.qsArgs, params.qsReset); 697 } else { 698 qs = params.qsReset ? "" : location.search; 699 } 700 url[i++] = qs; 701 702 return url.join(""); 703 }; 704 705 AjxUtil.byNumber = function(a, b) { 706 return Number(a) - Number(b); 707 }; 708 709 /** 710 * <strong>Note:</strong> 711 * This function <em>must</em> be wrapped in a closure that passes 712 * the property name as the first argument. 713 * 714 * @param {string} prop Property name. 715 * @param {object} a Object A. 716 * @param {object} b Object B. 717 */ 718 AjxUtil.byStringProp = function(prop, a, b) { 719 return a[prop].localeCompare(b[prop]); 720 }; 721 722 /** 723 * returns the size of the given array, i.e. the number of elements in it, regardless of whether the array is associative or not. 724 * so for example for array that is set simply by a = []; a[50] = "abc"; arraySize(a) == 1. For b = []; b["abc"] = "def"; arraySize(b) == 1 too. 725 * Incredibly JavasCript does not have a built in simple way to get that. 726 * @param arr 727 */ 728 AjxUtil.arraySize = 729 function(a) { 730 var size = 0; 731 for(var e in a) { 732 if (a.hasOwnProperty(e)) { 733 size ++; 734 } 735 } 736 return size; 737 }; 738 739 /** 740 * mergesort+dedupe 741 **/ 742 AjxUtil.mergeArrays = 743 function(arr1, arr2, orderfunc) { 744 if(!orderfunc) { 745 orderfunc = AjxUtil.__mergeArrays_orderfunc; 746 } 747 var tmpArr1 = []; 748 var cnt1 = arr1.length; 749 for(var i = 0; i < cnt1; i++) { 750 tmpArr1.push(arr1[i]); 751 } 752 753 var tmpArr2 = []; 754 var cnt2 = arr2.length; 755 for(var i = 0; i < cnt2; i++) { 756 tmpArr2.push(arr2[i]); 757 } 758 var resArr = []; 759 while (tmpArr1.length>0 && tmpArr2.length>0) { 760 if (orderfunc(tmpArr1[0],resArr[resArr.length-1])==0) { 761 tmpArr1.shift(); 762 continue; 763 } 764 765 if (orderfunc(tmpArr2[0],resArr[resArr.length-1])==0) { 766 tmpArr2.shift(); 767 continue; 768 } 769 770 if (orderfunc(tmpArr1[0], tmpArr2[0])<0) { 771 resArr.push(tmpArr1.shift()); 772 } else if (orderfunc(tmpArr1[0],tmpArr2[0])==0) { 773 resArr.push(tmpArr1.shift()); 774 tmpArr2.shift(); 775 } else { 776 resArr.push(tmpArr2.shift()); 777 } 778 } 779 780 while (tmpArr1.length>0) { 781 if (orderfunc(tmpArr1[0],resArr[resArr.length-1])==0) { 782 tmpArr1.shift(); 783 continue; 784 } 785 resArr.push(tmpArr1.shift()); 786 } 787 788 while (tmpArr2.length>0) { 789 if (orderfunc(tmpArr2[0], resArr[resArr.length-1])==0) { 790 tmpArr2.shift(); 791 continue; 792 } 793 resArr.push(tmpArr2.shift()); 794 } 795 return resArr; 796 }; 797 798 AjxUtil.__mergeArrays_orderfunc = function (val1,val2) { 799 if(val1>val2) return 1; 800 if (val1 < val2) return -1; 801 if(val1 == val2) return 0; 802 }; 803 804 AjxUtil.arraySubtract = function (arr1, arr2, orderfunc) { 805 if(!orderfunc) { 806 orderfunc = function (val1,val2) { 807 if(val1>val2) 808 return 1; 809 if (val1 < val2) 810 return -1; 811 if(val1 == val2) 812 return 0; 813 } 814 } 815 var tmpArr1 = []; 816 var cnt1 = arr1.length; 817 for(var i = 0; i < cnt1; i++) { 818 tmpArr1.push(arr1[i]); 819 } 820 821 var tmpArr2 = []; 822 var cnt2 = arr2.length; 823 for(var i = 0; i < cnt2; i++) { 824 tmpArr2.push(arr2[i]); 825 } 826 tmpArr2.sort(orderfunc); 827 tmpArr1.sort(orderfunc); 828 var resArr = []; 829 while(tmpArr1.length > 0 && tmpArr2.length > 0) { 830 if(orderfunc(tmpArr1[0],tmpArr2[0])==0) { 831 tmpArr1.shift(); 832 tmpArr2.shift(); 833 continue; 834 } 835 836 if(orderfunc(tmpArr1[0],tmpArr2[0]) < 0) { 837 resArr.push(tmpArr1.shift()); 838 continue; 839 } 840 841 if(orderfunc(tmpArr1[0],tmpArr2[0]) > 0) { 842 tmpArr2.shift(); 843 continue; 844 } 845 } 846 847 while(tmpArr1.length > 0) { 848 resArr.push(tmpArr1.shift()); 849 } 850 851 return resArr; 852 }; 853 854 // Support deprecated, misspelled version 855 AjxUtil.arraySubstract = AjxUtil.arraySubtract; 856 857 /** 858 * Returns the keys of the given hash as a sorted list. 859 * 860 * @param hash [hash] 861 */ 862 AjxUtil.getHashKeys = 863 function(hash) { 864 865 var list = []; 866 for (var key in hash) { 867 list.push(key); 868 } 869 list.sort(); 870 871 return list; 872 }; 873 874 /** 875 * Does a shallow comparison of two arrays. 876 * 877 * @param arr1 [array] 878 * @param arr2 [array] 879 */ 880 AjxUtil.arrayCompare = 881 function(arr1, arr2) { 882 if ((!arr1 || !arr2) && (arr1 != arr2)) { 883 return false; 884 } 885 if (arr1.length != arr2.length) { 886 return false; 887 } 888 for (var i = 0; i < arr1.length; i++) { 889 if (arr1[i] != arr2[i]) { 890 return false; 891 } 892 } 893 return true; 894 }; 895 896 /** 897 * Does a shallow comparison of two hashes. 898 * 899 * @param hash1 [hash] 900 * @param hash2 [hash] 901 */ 902 AjxUtil.hashCompare = 903 function(hash1, hash2) { 904 905 var keys1 = AjxUtil.getHashKeys(hash1); 906 var keys2 = AjxUtil.getHashKeys(hash2); 907 if (!AjxUtil.arrayCompare(keys1, keys2)) { 908 return false; 909 } 910 for (var i = 0, len = keys1.length; i < len; i++) { 911 var key = keys1[i]; 912 if (hash1[key] != hash2[key]) { 913 return false; 914 } 915 } 916 return true; 917 }; 918 919 /** 920 * Returns a shallow copy of the given hash. 921 * 922 * @param {Object} hash source hash 923 * @param {Array} omit Keys to skip (blacklist) 924 * @param {Array} keep Keys to keep (whitelist) 925 */ 926 AjxUtil.hashCopy = function(hash, omit, keep) { 927 928 omit = omit && AjxUtil.arrayAsHash(omit); 929 keep = keep && AjxUtil.arrayAsHash(keep); 930 931 var copy = {}; 932 for (var key in hash) { 933 if ((!omit || !omit[key]) && (!keep || keep[key])) { 934 copy[key] = hash[key]; 935 } 936 } 937 938 return copy; 939 }; 940 941 /** 942 * Updates one hash with values from another. 943 * 944 * @param {Object} hash1 Hash to be updated 945 * @param {Object} hash2 Hash to update from (values from hash2 will be copied to hash1) 946 * @param {Boolean} overwrite Set to true if existing values in hash1 should be overwritten when keys match (defaults to false) 947 * @param {Array} omit Keys to skip (blacklist) 948 * @param {Array} keep Keys to keep (whitelist) 949 */ 950 AjxUtil.hashUpdate = function(hash1, hash2, overwrite, omit, keep) { 951 952 omit = omit && AjxUtil.arrayAsHash(omit); 953 keep = keep && AjxUtil.arrayAsHash(keep); 954 955 for (var key in hash2) { 956 if ((overwrite || !(key in hash1)) && (!omit || !omit[key]) && (!keep || keep[key])) { 957 hash1[key] = hash2[key]; 958 } 959 } 960 }; 961 962 // array check that doesn't rely on instanceof, since type info 963 // can get lost in new window 964 AjxUtil.isArray1 = 965 function(arg) { 966 return !!(arg && (arg.length != null) && arg.splice && arg.slice); 967 }; 968 969 // converts the arg to an array if it isn't one 970 AjxUtil.toArray = 971 function(arg) { 972 if (!arg) { 973 return []; 974 } 975 else if (AjxUtil.isArray1(arg)) { 976 return arg; 977 } 978 else if (AjxUtil.isArrayLike(arg)) { 979 try { 980 // fails in IE8 981 return Array.prototype.slice.call(arg); 982 } catch (e) { 983 return AjxUtil.map(arg); 984 } 985 } 986 else if (arg.isAjxVector) { 987 return arg.getArray(); 988 } 989 else { 990 return [arg]; 991 } 992 }; 993 994 /** 995 * Returns a sub-property of an object. This is useful to avoid code like 996 * the following: 997 * <pre> 998 * resp = resp && resp.BatchResponse; 999 * resp = resp && resp.GetShareInfoResponse; 1000 * resp = resp && resp[0]; 1001 * </pre> 1002 * <p> 1003 * The first argument to this function is the source object while the 1004 * remaining arguments are the property names of the path to follow. 1005 * This is done instead of as a path string (e.g. "foo/bar[0]") to 1006 * avoid unnecessary parsing. 1007 * 1008 * @param {object} object The source object. 1009 * @param {string|number} ... The property of the current context object. 1010 */ 1011 AjxUtil.get = function(object /* , propName1, ... */) { 1012 for (var i = 1; object && i < arguments.length; i++) { 1013 object = object[arguments[i]]; 1014 } 1015 return object; 1016 }; 1017 1018 1019 /** 1020 * Convert non-ASCII characters to valid HTML UNICODE entities 1021 * @param {string} 1022 * 1023 */ 1024 AjxUtil.convertToEntities = function (source){ 1025 var result = ''; 1026 var length = 0; 1027 1028 if (!source || !(length = source.length)) return source; 1029 1030 for (var i = 0; i < length; i++) { 1031 var charCode = source.charCodeAt(i); 1032 // Encode non-ascii or double quotes 1033 if ((charCode > 127) || (charCode == 34)) { 1034 var temp = charCode.toString(10); 1035 while (temp.length < 4) { 1036 temp = '0' + temp; 1037 } 1038 result += '&#' + temp + ';'; 1039 } else { 1040 result += source.charAt(i); 1041 } 1042 } 1043 return result; 1044 }; 1045 1046 /** 1047 * Get the class attribute string from the given class. 1048 * @param {array} - An array of class names to be converted to a class attribute. 1049 * returns the attribute string with the class names or empty string if no class is passed. 1050 * 1051 */ 1052 AjxUtil.getClassAttr = function (classes){ 1053 var attr = []; 1054 if (classes && classes.length > 0) { 1055 //remove duplicate css classes 1056 classes = AjxUtil.uniq(classes); 1057 return ["class='" , classes.join(" "), "'"].join(""); 1058 } 1059 return ""; 1060 }; 1061 1062 /** 1063 * converts datauri string to blob object used for uploading the image 1064 * @param {dataURI} - datauri string data:image/png;base64,iVBORw0 1065 * 1066 */ 1067 AjxUtil.dataURItoBlob = 1068 function (dataURI) { 1069 1070 if (!(dataURI && typeof window.atob === "function" && typeof window.Blob === "function")) { 1071 return; 1072 } 1073 1074 var dataURIArray = dataURI.split(","); 1075 if (dataURIArray.length === 2) { 1076 if (dataURIArray[0].indexOf('base64') === -1) { 1077 return; 1078 } 1079 // convert base64 to raw binary data held in a string 1080 // doesn't handle URLEncoded DataURIs 1081 try{ 1082 var byteString = window.atob(dataURIArray[1]); 1083 } 1084 catch(e){ 1085 return; 1086 } 1087 if (!byteString) { 1088 return; 1089 } 1090 // separate out the mime component 1091 var mimeString = dataURIArray[0].split(':'); 1092 if (!mimeString[1]) { 1093 return; 1094 } 1095 mimeString = mimeString[1].split(';')[0]; 1096 if (mimeString) { 1097 // write the bytes of the string to an ArrayBuffer 1098 var byteStringLength = byteString.length, 1099 ab = new ArrayBuffer(byteStringLength), 1100 ia = new Uint8Array(ab); 1101 for (var i = 0; i < byteStringLength; i++) { 1102 ia[i] = byteString.charCodeAt(i); 1103 } 1104 return new Blob([ab], {"type" : mimeString}); 1105 } 1106 } 1107 1108 }; 1109 1110 AjxUtil.reduce = function(array, callback, opt_initialValue) { 1111 var reducefn = Array.prototype.reduce; 1112 1113 if (reducefn) { 1114 return reducefn.call(array, callback, opt_initialValue); 1115 } else { 1116 // polyfill from the Mozilla Developer Network for browsers without 1117 // reduce -- i.e. IE8. 1118 1119 if (array === null || 'undefined' === typeof array) { 1120 throw new TypeError('AjxUtil.reduce called on null or undefined'); 1121 } 1122 if ('function' !== typeof callback) { 1123 throw new TypeError(callback + ' is not a function'); 1124 } 1125 var index, value, 1126 length = array.length >>> 0, 1127 isValueSet = false; 1128 if (1 < arguments.length) { 1129 value = opt_initialValue; 1130 isValueSet = true; 1131 } 1132 for (index = 0; length > index; ++index) { 1133 if (array.hasOwnProperty(index)) { 1134 if (isValueSet) { 1135 value = callback(value, array[index], index, array); 1136 } 1137 else { 1138 value = array[index]; 1139 isValueSet = true; 1140 } 1141 } 1142 } 1143 if (!isValueSet) { 1144 throw new TypeError('Reduce of empty array with no initial value'); 1145 } 1146 return value; 1147 } 1148 1149 }; 1150 1151 1152 /** 1153 * Returns a value for the brightness of the given color. 1154 * 1155 * @param {string} rgb RGB value as #RRGGBB 1156 * @returns {number} number between 0 and 255 (higher is brighter) 1157 */ 1158 AjxUtil.getBrightness = function(rgb) { 1159 1160 var r, g, b; 1161 1162 if (rgb && rgb.length === 7 && rgb.indexOf('#') === 0) { 1163 rgb = rgb.substr(1); 1164 } 1165 else { 1166 return null; 1167 } 1168 1169 r = parseInt(rgb.substr(0, 2), 16); 1170 g = parseInt(rgb.substr(2, 2), 16); 1171 b = parseInt(rgb.substr(4, 2), 16); 1172 1173 // http://alienryderflex.com/hsp.html 1174 return Math.sqrt( 1175 r * r * .299 + 1176 g * g * .587 + 1177 b * b * .114 1178 ); 1179 }; 1180 1181 /** 1182 * Returns the better foreground color based on contrast with the given background color. 1183 * 1184 * @param {string} bgColor RGB value as #RRGGBB 1185 * @returns {string} 'black' or 'white' 1186 */ 1187 AjxUtil.getForegroundColor = function(bgColor) { 1188 var brightness = AjxUtil.getBrightness(bgColor); 1189 return (brightness != null && brightness < 130) ? 'white' : 'black'; 1190 }; 1191 1192 1193