1 /* 2 * ilib.js - define the ilib name space 3 * 4 * Copyright © 2012-2018, JEDLSoft 5 * 6 * Licensed under the Apache License, Version 2.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 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * 16 * See the License for the specific language governing permissions and 17 * limitations under the License. 18 */ 19 20 /** 21 * @namespace The global namespace that contains general ilib functions useful 22 * to all of ilib 23 * 24 * @version // !macro ilibVersion 25 */ 26 var ilib = ilib || {}; 27 28 /** @private */ 29 ilib._ver = function() { 30 return // !macro ilibVersion 31 ; 32 }; 33 34 /** 35 * Return the current version of ilib. 36 * 37 * @static 38 * @return {string} a version string for this instance of ilib 39 */ 40 ilib.getVersion = function () { 41 if (ilib._dyncode) { 42 try { 43 var pkg; 44 pkg = require("../package.json"); 45 return pkg.version; 46 } catch (e) { 47 // ignore 48 } 49 } 50 return ilib._ver() || "14.0"; 51 }; 52 53 /** 54 * Place where resources and such are eventually assigned. 55 */ 56 ilib.data = { 57 /** @type {{ccc:Object.<string,number>,nfd:Object.<string,string>,nfc:Object.<string,string>,nfkd:Object.<string,string>,nfkc:Object.<string,string>}} */ 58 norm: { 59 ccc: {}, 60 nfd: {}, 61 nfc: {}, 62 nfkd: {}, 63 nfkc: {} 64 }, 65 zoneinfo: { 66 "Etc/UTC":{"o":"0:0","f":"UTC"}, 67 "local":{"f":"local"} 68 }, 69 /** @type {Object.<string,{to:Object.<string,string>,from:Object.<string,number>}>} */ charmaps: {}, 70 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype: null, 71 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_c: null, 72 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_l: null, 73 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_m: null, 74 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_p: null, 75 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_z: null, 76 /** @type {null|Object.<string,Array.<Array.<number>>>} */ scriptToRange: null, 77 /** @type {null|Object.<string,string|Object.<string|Object.<string,string>>>} */ dateformats: null, 78 /** @type {null|Array.<string>} */ timezones: [], 79 cache: {} 80 }; 81 82 /* 83 if (typeof(window) !== 'undefined') { 84 window["ilib"] = ilib; 85 } 86 */ 87 88 // export ilib for use as a module in nodejs 89 if (typeof(module) !== 'undefined') { 90 module.exports = ilib; 91 module.exports.ilib = ilib; // for backwards compatibility with older versions of ilib 92 } 93 94 /** 95 * Sets the pseudo locale. Pseudolocalization (or pseudo-localization) is used for testing 96 * internationalization aspects of software. Instead of translating the text of the software 97 * into a foreign language, as in the process of localization, the textual elements of an application 98 * are replaced with an altered version of the original language.These specific alterations make 99 * the original words appear readable, but include the most problematic characteristics of 100 * the world's languages: varying length of text or characters, language direction, and so on. 101 * Regular Latin pseudo locale: eu-ES and RTL pseudo locale: ps-AF 102 * 103 * @param {string|undefined|null} localename the locale specifier for the pseudo locale 104 */ 105 ilib.setAsPseudoLocale = function (localename) { 106 if (localename) { 107 ilib.pseudoLocales.push(localename) 108 } 109 }; 110 111 /** 112 * Reset the list of pseudo locales back to the default single locale of zxx-XX. 113 * @static 114 */ 115 ilib.clearPseudoLocales = function() { 116 ilib.pseudoLocales = [ 117 "zxx-XX", 118 "zxx-Cyrl-XX", 119 "zxx-Hans-XX", 120 "zxx-Hebr-XX" 121 ]; 122 }; 123 124 ilib.clearPseudoLocales(); 125 126 /** 127 * Return the name of the platform 128 * @private 129 * @static 130 * @return {string} string naming the platform 131 */ 132 ilib._getPlatform = function () { 133 if (!ilib._platform) { 134 try { 135 if (typeof(java.lang.Object) !== 'undefined') { 136 ilib._platform = (typeof(process) !== 'undefined') ? "trireme" : "rhino"; 137 return ilib._platform; 138 } 139 } catch (e) {} 140 141 if (typeof(global) !== 'undefined' && global.process && global.process.versions && global.process.versions.node && typeof(module) !== 'undefined') { 142 ilib._platform = "nodejs"; 143 } else if (typeof(Qt) !== 'undefined') { 144 ilib._platform = "qt"; 145 } else if (typeof(window) !== 'undefined') { 146 ilib._platform = (typeof(PalmSystem) !== 'undefined') ? "webos" : "browser"; 147 } else { 148 ilib._platform = "unknown"; 149 } 150 } 151 return ilib._platform; 152 }; 153 154 /** 155 * If this ilib is running in a browser, return the name of that browser. 156 * @private 157 * @static 158 * @return {string|undefined} the name of the browser that this is running in ("firefox", "chrome", "ie", 159 * "safari", or "opera"), or undefined if this is not running in a browser or if 160 * the browser name could not be determined 161 */ 162 ilib._getBrowser = function () { 163 var browser = undefined; 164 if (ilib._getPlatform() === "browser") { 165 if (navigator && navigator.userAgent) { 166 if (navigator.userAgent.indexOf("Firefox") > -1) { 167 browser = "firefox"; 168 } 169 if (navigator.userAgent.search(/Opera|OPR/) > -1 ) { 170 browser = "opera"; 171 } 172 if (navigator.userAgent.indexOf("Chrome") > -1) { 173 browser = "chrome"; 174 } 175 if (navigator.userAgent.indexOf(" .NET") > -1) { 176 browser = "ie"; 177 } 178 if (navigator.userAgent.indexOf("Safari") > -1) { 179 // chrome also has the string Safari in its userAgent, but the chrome case is 180 // already taken care of above 181 browser = "safari"; 182 } 183 if (navigator.userAgent.indexOf("Edge") > -1) { 184 browser = "Edge"; 185 } 186 if (navigator.userAgent.search(/iPad|iPhone|iPod/) > -1) { 187 // Due to constraints of the iOS platform, 188 // all browser must be built on top of the WebKit rendering engine 189 browser = "iOS"; 190 } 191 } 192 } 193 return browser; 194 }; 195 196 /** 197 * Return the value of the top object in the system. This could be global 198 * for node, or window for browsers, etc. 199 * @private 200 * @static 201 * @return {Object|undefined} the top variable, or undefined if there is none on this 202 * platform 203 */ 204 ilib._top = function() { 205 if (typeof(this.top) === 'undefined') { 206 this.top = null; 207 switch (ilib._getPlatform()) { 208 case "rhino": 209 this.top = (function() { 210 return (typeof global === 'object') ? global : this; 211 })(); 212 break; 213 case "nodejs": 214 case "trireme": 215 this.top = typeof(global) !== 'undefined' ? global : this; 216 //console.log("ilib._top: top is " + (typeof(global) !== 'undefined' ? "global" : "this")); 217 break; 218 default: 219 this.top = window; 220 break; 221 } 222 } 223 224 return this.top || undefined; 225 }; 226 227 /** 228 * Return the value of a global variable given its name in a way that works 229 * correctly for the current platform. 230 * @private 231 * @static 232 * @param {string} name the name of the variable to return 233 * @return {*} the global variable, or undefined if it does not exist 234 */ 235 ilib._global = function(name) { 236 var top = this._top(); 237 try { 238 return top[name]; 239 } catch (e) { 240 return undefined; 241 } 242 }; 243 244 /** 245 * Return true if the global variable is defined on this platform. 246 * @private 247 * @static 248 * @param {string} name the name of the variable to check 249 * @return {boolean} true if the global variable is defined on this platform, false otherwise 250 */ 251 ilib._isGlobal = function(name) { 252 return typeof(ilib._global(name)) !== 'undefined'; 253 }; 254 255 /** 256 * Clear the file load cache. This is mainly used by the unit tests, 257 * but could be used by regular callers if you want to free up memory 258 * for garbage collecting. 259 */ 260 ilib.clearCache = function() { 261 ilib.data.cache = {}; 262 }; 263 264 /** 265 * Sets the default locale for all of ilib. This locale will be used 266 * when no explicit locale is passed to any ilib class. If the default 267 * locale is not set, ilib will attempt to use the locale of the 268 * environment it is running in, if it can find that. If not, it will 269 * default to the locale "en-US". If a type of parameter is string, 270 * ilib will take only well-formed BCP-47 tag <p> 271 * 272 * 273 * @static 274 * @param {string|undefined|null} spec the locale specifier for the default locale 275 */ 276 ilib.setLocale = function (spec) { 277 if (typeof(spec) === 'string' || !spec) { 278 ilib.locale = spec; 279 } 280 // else ignore other data types, as we don't have the dependencies 281 // to look into them to find a locale 282 }; 283 284 /** 285 * Return the default locale for all of ilib if one has been set. This 286 * locale will be used when no explicit locale is passed to any ilib 287 * class. If the default 288 * locale is not set, ilib will attempt to use the locale of the 289 * environment it is running in, if it can find that. If not, it will 290 * default to the locale "en-US".<p> 291 * 292 * 293 * @static 294 * @return {string} the locale specifier for the default locale 295 */ 296 ilib.getLocale = function () { 297 if (typeof(ilib.locale) !== 'string') { 298 var plat = ilib._getPlatform(); 299 switch (plat) { 300 case 'browser': 301 // running in a browser 302 if(typeof(navigator.language) !== 'undefined') { 303 ilib.locale = navigator.language.substring(0,3) + navigator.language.substring(3,5).toUpperCase(); // FF/Opera/Chrome/Webkit 304 } 305 if (!ilib.locale) { 306 // IE on Windows 307 lang = typeof(navigator.browserLanguage) !== 'undefined' ? 308 navigator.browserLanguage : 309 (typeof(navigator.userLanguage) !== 'undefined' ? 310 navigator.userLanguage : 311 (typeof(navigator.systemLanguage) !== 'undefined' ? 312 navigator.systemLanguage : 313 undefined)); 314 if (typeof(lang) !== 'undefined' && lang) { 315 // for some reason, MS uses lower case region tags 316 ilib.locale = lang.substring(0,3) + lang.substring(3,5).toUpperCase(); 317 } 318 } 319 break; 320 case 'webos': 321 // webOS 322 if (typeof(PalmSystem.locales) !== 'undefined' && 323 typeof(PalmSystem.locales.UI) != 'undefined' && 324 PalmSystem.locales.UI.length > 0) { 325 ilib.locale = PalmSystem.locales.UI; 326 } else if (typeof(PalmSystem.locale) !== 'undefined') { 327 ilib.locale = PalmSystem.locale; 328 } 329 break; 330 case 'rhino': 331 if (typeof(environment) !== 'undefined' && environment.user && typeof(environment.user.language) === 'string' && environment.user.language.length > 0) { 332 // running under plain rhino 333 ilib.locale = environment.user.language; 334 if (typeof(environment.user.country) === 'string' && environment.user.country.length > 0) { 335 ilib.locale += '-' + environment.user.country; 336 } 337 } 338 break; 339 case "trireme": 340 // under trireme on rhino emulating nodejs 341 lang = process.env.LANG || process.env.LANGUAGE || process.env.LC_ALL; 342 // the LANG variable on unix is in the form "lang_REGION.CHARSET" 343 // where language and region are the correct ISO codes separated by 344 // an underscore. This translate it back to the BCP-47 form. 345 if (lang && typeof(lang) !== 'undefined') { 346 ilib.locale = lang.substring(0,2).toLowerCase() + '-' + lang.substring(3,5).toUpperCase(); 347 } 348 break; 349 case 'nodejs': 350 // running under nodejs 351 lang = global.process.env.LANG || global.process.env.LC_ALL; 352 // the LANG variable on unix is in the form "lang_REGION.CHARSET" 353 // where language and region are the correct ISO codes separated by 354 // an underscore. This translate it back to the BCP-47 form. 355 if (lang && typeof(lang) !== 'undefined') { 356 ilib.locale = lang.substring(0,2).toLowerCase() + '-' + lang.substring(3,5).toUpperCase(); 357 } 358 break; 359 case 'qt': 360 // running in the Javascript engine under Qt/QML 361 var locobj = Qt.locale(); 362 var lang = locobj.name && locobj.name.replace("_", "-") || "en-US"; 363 break; 364 } 365 ilib.locale = typeof(ilib.locale) === 'string' && ilib.locale ? ilib.locale : 'en-US'; 366 if (ilib.locale === "en") { 367 ilib.locale = "en-US"; // hack to get various platforms working correctly 368 } 369 } 370 return ilib.locale; 371 }; 372 373 /** 374 * Sets the default time zone for all of ilib. This time zone will be used when 375 * no explicit time zone is passed to any ilib class. If the default time zone 376 * is not set, ilib will attempt to use the time zone of the 377 * environment it is running in, if it can find that. If not, it will 378 * default to the the UTC zone "Etc/UTC".<p> 379 * 380 * 381 * @static 382 * @param {string} tz the name of the time zone to set as the default time zone 383 */ 384 ilib.setTimeZone = function (tz) { 385 ilib.tz = tz || ilib.tz; 386 }; 387 388 /** 389 * Return the default time zone for all of ilib if one has been set. This 390 * time zone will be used when no explicit time zone is passed to any ilib 391 * class. If the default time zone 392 * is not set, ilib will attempt to use the locale of the 393 * environment it is running in, if it can find that. If not, it will 394 * default to the the zone "local".<p> 395 * 396 * 397 * @static 398 * @return {string} the default time zone for ilib 399 */ 400 ilib.getTimeZone = function() { 401 if (typeof(ilib.tz) === 'undefined') { 402 if (typeof(Intl) !== 'undefined' && typeof(Intl.DateTimeFormat) !== 'undefined') { 403 var ro = new Intl.DateTimeFormat().resolvedOptions(); 404 ilib.tz = ro && ro.timeZone; 405 } 406 407 switch (ilib._getPlatform()) { 408 case 'browser': 409 // running in a browser 410 if (navigator.timezone && navigator.timezone.length > 0) { 411 ilib.tz = navigator.timezone; 412 } 413 break; 414 case 'webos': 415 // running in webkit on webOS 416 if (PalmSystem.timezone && PalmSystem.timezone.length > 0) { 417 ilib.tz = PalmSystem.timezone; 418 } 419 break; 420 case 'rhino': 421 // running under rhino 422 if (typeof(environment.user.timezone) !== 'undefined' && environment.user.timezone.length > 0) { 423 ilib.tz = environment.user.timezone; 424 } 425 break; 426 case 'nodejs': 427 if (global.process.env && typeof(global.process.env.TZ) !== "undefined") { 428 ilib.tz = global.process.env.TZ; 429 } 430 break; 431 } 432 433 ilib.tz = ilib.tz || "local"; 434 } 435 436 return ilib.tz; 437 }; 438 439 /** 440 * @class 441 * Defines the interface for the loader class for ilib. The main method of the 442 * loader object is loadFiles(), which loads a set of requested locale data files 443 * from where-ever it is stored. 444 * @interface 445 */ 446 ilib.Loader = function() {}; 447 448 /** 449 * Load a set of files from where-ever it is stored.<p> 450 * 451 * This is the main function define a callback function for loading missing locale 452 * data or resources. 453 * If this copy of ilib is assembled without including the required locale data 454 * or resources, then that data can be lazy loaded dynamically when it is 455 * needed by calling this method. Each ilib class will first 456 * check for the existence of data under ilib.data, and if it is not there, 457 * it will attempt to load it by calling this method of the laoder, and then place 458 * it there.<p> 459 * 460 * Suggested implementations of this method might load files 461 * directly from disk under nodejs or rhino, or within web pages, to load 462 * files from the server with XHR calls.<p> 463 * 464 * The first parameter to this method, paths, is an array of relative paths within 465 * the ilib dir structure for the 466 * requested data. These paths will already have the locale spec integrated 467 * into them, so no further tweaking needs to happen to load the data. Simply 468 * load the named files. The second 469 * parameter tells the loader whether to load the files synchronously or asynchronously. 470 * If the sync parameters is false, then the onLoad function must also be specified. 471 * The third parameter gives extra parameters to the loader passed from the calling 472 * code. This may contain any property/value pairs. The last parameter, callback, 473 * is a callback function to call when all of the data is finishing loading. Make 474 * sure to call the callback with the context of "this" so that the caller has their 475 * context back again.<p> 476 * 477 * The loader function must be able to operate either synchronously or asychronously. 478 * If the loader function is called with an undefined callback function, it is 479 * expected to load the data synchronously, convert it to javascript 480 * objects, and return the array of json objects as the return value of the 481 * function. If the loader 482 * function is called with a callback function, it may load the data 483 * synchronously or asynchronously (doesn't matter which) as long as it calls 484 * the callback function with the data converted to a javascript objects 485 * when it becomes available. If a particular file could not be loaded, the 486 * loader function should put undefined into the corresponding entry in the 487 * results array. 488 * Note that it is important that all the data is loaded before the callback 489 * is called.<p> 490 * 491 * An example implementation for nodejs might be: 492 * 493 * <pre> 494 * var fs = require("fs"); 495 * 496 * var myLoader = function() {}; 497 * myLoader.prototype = new Loader(); 498 * myLoader.prototype.constructor = myLoader; 499 * myLoader.prototype.loadFiles = function(paths, sync, params, callback) { 500 * if (sync) { 501 * var ret = []; 502 * // synchronous load -- just return the result 503 * paths.forEach(function (path) { 504 * var json = fs.readFileSync(path, "utf-8"); 505 * ret.push(json ? JSON.parse(json) : undefined); 506 * }); 507 * 508 * return ret; 509 * } 510 * this.callback = callback; 511 * 512 * // asynchronous 513 * this.results = []; 514 * this._loadFilesAsync(paths); 515 * } 516 * myLoader.prototype._loadFilesAsync = function (paths) { 517 * if (paths.length > 0) { 518 * var file = paths.shift(); 519 * fs.readFile(file, "utf-8", function(err, json) { 520 * this.results.push(err ? undefined : JSON.parse(json)); 521 * // call self recursively so that the callback is only called at the end 522 * // when all the files are loaded sequentially 523 * if (paths.length > 0) { 524 * this._loadFilesAsync(paths); 525 * } else { 526 * this.callback(this.results); 527 * } 528 * }); 529 * } 530 * } 531 * 532 * // bind to "this" so that "this" is relative to your own instance 533 * ilib.setLoaderCallback(new myLoader()); 534 * </pre> 535 536 * @param {Array.<string>} paths An array of paths to load from wherever the files are stored 537 * @param {Boolean} sync if true, load the files synchronously, and false means asynchronously 538 * @param {Object} params an object with any extra parameters for the loader. These can be 539 * anything. The caller of the ilib class passes these parameters in. Presumably, the code that 540 * calls ilib and the code that provides the loader are together and can have a private 541 * agreement between them about what the parameters should contain. 542 * @param {function(Object)} callback function to call when the files are all loaded. The 543 * parameter of the callback function is the contents of the files. 544 */ 545 ilib.Loader.prototype.loadFiles = function (paths, sync, params, callback) {}; 546 547 /** 548 * Return all files available for loading using this loader instance. 549 * This method returns an object where the properties are the paths to 550 * directories where files are loaded from and the values are an array 551 * of strings containing the relative paths under the directory of each 552 * file that can be loaded.<p> 553 * 554 * Example: 555 * <pre> 556 * { 557 * "/usr/share/javascript/ilib/locale": [ 558 * "dateformats.json", 559 * "aa/dateformats.json", 560 * "af/dateformats.json", 561 * "agq/dateformats.json", 562 * "ak/dateformats.json", 563 * ... 564 * "zxx/dateformats.json" 565 * ] 566 * } 567 * </pre> 568 * @returns {Object} a hash containing directory names and 569 * paths to file that can be loaded by this loader 570 */ 571 ilib.Loader.prototype.listAvailableFiles = function() {}; 572 573 /** 574 * Return true if the file in the named path is available for loading using 575 * this loader. The path may be given as an absolute path, in which case 576 * only that file is checked, or as a relative path, in which case, the 577 * relative path may appear underneath any of the directories that the loader 578 * knows about. 579 * @returns {boolean} true if the file in the named path is available for loading, and 580 * false otherwise 581 */ 582 ilib.Loader.prototype.isAvailable = function(path) {}; 583 584 /** 585 * Set the custom loader used to load ilib's locale data in your environment. 586 * The instance passed in must implement the Loader interface. See the 587 * Loader class documentation for more information about loaders. 588 * 589 * @static 590 * @param {ilib.Loader} loader class to call to access the requested data. 591 * @return {boolean} true if the loader was installed correctly, or false 592 * if not 593 */ 594 ilib.setLoaderCallback = function(loader) { 595 // only a basic check 596 if ((typeof(loader) === 'object' && typeof(loader.loadFiles) === 'function') || 597 typeof(loader) === 'function' || typeof(loader) === 'undefined') { 598 //console.log("setting callback loader to " + (loader ? loader.name : "undefined")); 599 ilib._load = loader; 600 return true; 601 } 602 return false; 603 }; 604 605 /** 606 * Return the custom Loader instance currently in use with this instance 607 * of ilib. If there is no loader, this method returns undefined. 608 * 609 * @protected 610 * @static 611 * @return {ilib.Loader|undefined} the loader instance currently in use, or 612 * undefined if there is no such loader 613 */ 614 ilib.getLoader = function() { 615 return ilib._load; 616 }; 617 618 /** 619 * Test whether an object is an javascript array. 620 * 621 * @static 622 * @param {*} object The object to test 623 * @return {boolean} return true if the object is an array 624 * and false otherwise 625 */ 626 ilib.isArray = function(object) { 627 if (typeof(object) === 'object') { 628 return Object.prototype.toString.call(object) === '[object Array]'; 629 } 630 return false; 631 }; 632 633 /** 634 * Extend object1 by mixing in everything from object2 into it. The objects 635 * are deeply extended, meaning that this method recursively descends the 636 * tree in the objects and mixes them in at each level. Arrays are extended 637 * by concatenating the elements of object2 onto those of object1. 638 * 639 * @static 640 * @param {Object} object1 the target object to extend 641 * @param {Object=} object2 the object to mix in to object1 642 * @return {Object} returns object1 643 */ 644 ilib.extend = function (object1, object2) { 645 var prop = undefined; 646 if (object2) { 647 for (prop in object2) { 648 // don't extend object with undefined or functions 649 if (prop && typeof(object2[prop]) !== 'undefined' && typeof(object2[prop]) !== "function") { 650 if (ilib.isArray(object1[prop]) && ilib.isArray(object2[prop])) { 651 //console.log("Merging array prop " + prop); 652 object1[prop] = object1[prop].concat(object2[prop]); 653 } else if (typeof(object1[prop]) === 'object' && typeof(object2[prop]) === 'object') { 654 //console.log("Merging object prop " + prop); 655 if (prop !== "ilib") { 656 object1[prop] = ilib.extend(object1[prop], object2[prop]); 657 } 658 } else { 659 //console.log("Copying prop " + prop); 660 // for debugging. Used to determine whether or not json files are overriding their parents unnecessarily 661 object1[prop] = object2[prop]; 662 } 663 } 664 } 665 } 666 return object1; 667 }; 668 669 ilib.extend2 = function (object1, object2) { 670 var prop = undefined; 671 if (object2) { 672 for (prop in object2) { 673 // don't extend object with undefined or functions 674 if (prop && typeof(object2[prop]) !== 'undefined') { 675 if (ilib.isArray(object1[prop]) && ilib.isArray(object2[prop])) { 676 //console.log("Merging array prop " + prop); 677 object1[prop] = object1[prop].concat(object2[prop]); 678 } else if (typeof(object1[prop]) === 'object' && typeof(object2[prop]) === 'object') { 679 //console.log("Merging object prop " + prop); 680 if (prop !== "ilib") { 681 object1[prop] = ilib.extend2(object1[prop], object2[prop]); 682 } 683 } else { 684 //console.log("Copying prop " + prop); 685 // for debugging. Used to determine whether or not json files are overriding their parents unnecessarily 686 object1[prop] = object2[prop]; 687 } 688 } 689 } 690 } 691 return object1; 692 }; 693 694 /** 695 * If Function.prototype.bind does not exist in this JS engine, this 696 * function reimplements it in terms of older JS functions. 697 * bind() doesn't exist in many older browsers. 698 * 699 * @static 700 * @param {Object} scope object that the method should operate on 701 * @param {function(...)} method method to call 702 * @return {function(...)|undefined} function that calls the given method 703 * in the given scope with all of its arguments properly attached, or 704 * undefined if there was a problem with the arguments 705 */ 706 ilib.bind = function(scope, method/*, bound arguments*/){ 707 if (!scope || !method) { 708 return undefined; 709 } 710 711 /** @protected 712 * @param {Arguments} inArrayLike 713 * @param {number=} inOffset 714 */ 715 function cloneArray(inArrayLike, inOffset) { 716 var arr = []; 717 for(var i = inOffset || 0, l = inArrayLike.length; i<l; i++){ 718 arr.push(inArrayLike[i]); 719 } 720 return arr; 721 } 722 723 if (typeof(method) === 'function') { 724 var func, args = cloneArray(arguments, 2); 725 if (typeof(method.bind) === 'function') { 726 func = method.bind.apply(method, [scope].concat(args)); 727 } else { 728 func = function() { 729 var nargs = cloneArray(arguments); 730 // invoke with collected args 731 return method.apply(scope, args.concat(nargs)); 732 }; 733 } 734 return func; 735 } 736 return undefined; 737 }; 738 739 /** 740 * @private 741 */ 742 ilib._dyncode = false; 743 744 /** 745 * Return true if this copy of ilib is using dynamically loaded code. It returns 746 * false for pre-assembled code. 747 * 748 * @static 749 * @return {boolean} true if this ilib uses dynamically loaded code, and false otherwise 750 */ 751 ilib.isDynCode = function() { 752 return ilib._dyncode; 753 }; 754 755 /** 756 * @private 757 */ 758 ilib._dyndata = false; 759 760 /** 761 * Return true if this copy of ilib is using dynamically loaded locale data. It returns 762 * false for pre-assembled data. 763 * 764 * @static 765 * @return {boolean} true if this ilib uses dynamically loaded locale data, and false otherwise 766 */ 767 ilib.isDynData = function() { 768 return ilib._dyndata; 769 }; 770 771 ilib._loadtime = new Date().getTime(); 772