1 /* 2 * Copyright (C) 2012-2013 DFKI GmbH 3 * Deutsches Forschungszentrum fuer Kuenstliche Intelligenz 4 * German Research Center for Artificial Intelligence 5 * http://www.dfki.de 6 * 7 * Permission is hereby granted, free of charge, to any person obtaining a 8 * copy of this software and associated documentation files (the 9 * "Software"), to deal in the Software without restriction, including 10 * without limitation the rights to use, copy, modify, merge, publish, 11 * distribute, sublicense, and/or sell copies of the Software, and to 12 * permit persons to whom the Software is furnished to do so, subject to 13 * the following conditions: 14 * 15 * The above copyright notice and this permission notice shall be included 16 * in all copies or substantial portions of the Software. 17 * 18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 */ 26 27 28 define(['constants', 'stringExtension', 'jquery', 'paramsParseFunc', 'logger', 'module'], 29 /** 30 * A Utility class to support various functions.<br> 31 * 32 * 33 * @class mmir.CommonUtils 34 * @name mmir.CommonUtils 35 * @static 36 * 37 * @public 38 * 39 * @requires StringExtensions 40 * @requires Constants (optionally: jQuery) 41 * @requires mmir.SemanticInterpreter (in {@link mmir.CommonUtils#loadCompiledGrammars}) 42 * 43 * @requires jQuery.isArray in #isArrayHelper 44 * @requires jQuery.Deferred in #loadImpl, #loadDirectoryStructure, #setToCompatibilityMode 45 * @requires jQuery.ajax in #loadDirectoryStructure 46 * 47 * 48 * @requires jQuery in #resizeFitToSourroundingBox 49 * 50 * 51 * @example var isList = mmir.CommonUtils.isArray(list); 52 * 53 */ 54 function( 55 constants, stringExt, $, paramsParseFunc, Logger, module 56 ) { 57 /** @scope mmir.CommonUtils.prototype *///for jsdoc2 58 59 /** 60 * @private 61 * @type CommonUtils 62 * @memberOf mmir.CommonUtils# 63 */ 64 var instance = null; 65 66 /** 67 * @private 68 * @type Logger 69 * @memberOf mmir.CommonUtils# 70 */ 71 var logger = Logger.create(module); 72 73 /** 74 * JSON-Object containing the directory Structure of the application. Only 75 * directories defined by the Property 76 * {@link CommonUtils-constructor-directoriesToParse} are contained 77 * within the JSON-Object. 78 * 79 * @type JSON 80 * @private 81 * @memberOf mmir.CommonUtils# 82 */ 83 this.directoryStructure; 84 85 /** 86 * This helper initializes a function for detecting if an Object is an 87 * Array. 88 * 89 * The helper tries to find functions of JavaScript libraries for this; if 90 * none can be found, a custom implementation is used. 91 * 92 * The returned function is used by {@link mmir.CommonUtils#isArray} 93 * 94 * NOTE: The current implementation checks jQuery.isArray for presences 95 * 96 * @function 97 * @private 98 * @returns {Function} a function that takes one parameter (Object) and 99 * returns true if this parameter is an Array (false otherwise) 100 * 101 * @memberOf mmir.CommonUtils# 102 */ 103 var isArrayHelper = function(obj) { 104 105 // this is the initializer: the following will overwrite the 106 // isArray-function 107 // with the appropriate version (use jQuery method, if present, 108 // otherwise use alternative) 109 110 // if present, use jQuery method: 111 if (typeof $ !== 'undefined' && typeof $.isArray === 'function') { 112 isArrayHelper = $.isArray; 113 } 114 else { 115 // use the toString method with well-defined return-value from 116 // Object: 117 var staticToString = Object.prototype.toString; 118 119 isArrayHelper = function(obj) { 120 return staticToString.call(obj) === '[object Array]'; 121 }; 122 } 123 }; 124 // initialize the isArray-function 125 isArrayHelper(); 126 127 /** 128 * Constructor-Method of Class {@link mmir.CommonUtils} 129 * 130 * @param {jQuery} 131 * [$] the jQuery instance/object (OPTIONAL); some few function 132 * need jQuery to work correctly (see requires annotations) 133 * 134 * @constructs mmir.CommonUtils 135 * @memberOf mmir.CommonUtils# 136 * @function 137 * @private 138 */ 139 function constructor($, constants) { 140 // private members. 141 142 /** 143 * The Prefix for the file names of partial-files.<br> 144 * Files named <PARTIAL-PREFIX>filename.ehtml inside a 145 * views-directory are recognized as partials. 146 * 147 * @type String 148 * @private 149 */ 150 var partialsPrefix = '~'; 151 152 /** 153 * Array of Directories (Strings) to parse at the starting process<br> 154 * those directories are then accessable by the functions 155 * {@link mmir.CommonUtils#getDirectoryContents} and 156 * {@link mmir.CommonUtils#getDirectoryContentsWithFilter} 157 * 158 * TODO read from properties (implement mechanism such that 159 * \build.settings and this refer to the same resource) 160 * 161 * @type Array 162 * @private 163 */ 164 var directoriesToParse = [ 165 "www/controllers", 166 "www/views", 167 "www/models", 168 "www/config", 169 "www/mmirf/plugins", 170 "www/helpers" 171 ]; 172 173 /** @lends mmir.CommonUtils.prototype */ 174 return { 175 176 /** 177 * This function is used by 178 * {@link mmir.CommonUtils#getDirectoryContents} and 179 * {@link mmir.CommonUtils#getDirectoryContentsWithFilter} to strip the 180 * pathname parameter 181 * 182 * @function 183 * @private 184 * @param {string} 185 * pathname The path that should be stripped of "file://" and a 186 * beginning or trailing "/" 187 * @returns {String} The stripped pathname - devoid of beginning "file://" 188 * or "/" and trailing "/" 189 * 190 * @memberOf mmir.CommonUtils.prototype 191 */ 192 stripPathName: function(pathname) { 193 194 // FIXME this is a HACK; TODO handle this in a general way! 195 var basePath = constants.getBasePath(); 196 197 if (pathname.startsWith(basePath)) { 198 pathname = pathname.substring(basePath.length); 199 } 200 201 if (pathname.indexOf("file://") != -1) { 202 pathname = pathname.replace("file://", ""); 203 } 204 if (pathname[pathname.length - 1] == "/") { 205 pathname = pathname.substring(0, pathname.length - 1); 206 } 207 if (pathname[0] != "/") { 208 pathname = "/" + pathname; 209 } 210 211 return pathname; 212 }, 213 214 // public members. 215 /** 216 * @function 217 * @public 218 * @returns {String} The Prefix for the file names of partial-files 219 * @memberOf mmir.CommonUtils.prototype 220 */ 221 getPartialsPrefix : function() { 222 return partialsPrefix; 223 }, 224 225 /** 226 * @function 227 * @public 228 * @returns {Object} Directory structure as json object 229 * @memberOf mmir.CommonUtils.prototype 230 */ 231 getDirectoryStructure : function() { 232 return this.directoryStructure; 233 }, 234 /** 235 * extracts all the strings from a String-Array into a single string 236 * 237 * @function 238 * @public 239 * @returns {string} text 240 * @memberOf mmir.CommonUtils# 241 */ 242 concatArray : function(array) { 243 return array.join(', '); 244 }, 245 /** 246 * Regular Expression for matching HTML comments.<br> 247 * 248 * This RegExp also matches multi-line comments. 249 * 250 * Note upon using the RegExp that it does not consider if a HTML 251 * comment is specified within a String or data-definition (i.e. the 252 * comment is matched regardless were its defined). 253 * 254 * @type String|RegExp 255 * @public 256 * @memberOf mmir.CommonUtils.prototype 257 * 258 * @example <!-- some comment --> 259 */ 260 regexHTMLComment : /<!--([\r\n]|.)*?-->/igm, 261 262 /** 263 * Similar to the jQuery.getScript() function - appending a url of a 264 * javascript-source to the header of the main document.<br> 265 * This function also calls a callback if the script was loaded. 266 * 267 * @function 268 * @param {String} 269 * url source of javascript-file 270 * @param {Function} 271 * callback callback function 272 * @public 273 * @async 274 * @deprecated instead use #getLocalScript() 275 * @memberOf mmir.CommonUtils.prototype 276 */ 277 loadScript : function(url, callback) { 278 var script = document.createElement("script"); 279 script.type = "text/javascript"; 280 script.src = url; 281 282 if (typeof callback === 'function') { 283 /** @ignore */ 284 script.onload = function() { 285 callback(); 286 }; 287 } 288 document.getElementsByTagName("head")[0].appendChild(script); 289 }, 290 291 /** 292 * Load all plugins (i.e. JavaScript interfaces for 293 * Cordova/Java-Impl. plugins). 294 * 295 * @function 296 * @param {String} [pluginsPath] OPTIONAL 297 * Path of the plugins which should be 298 * loaded, e.g. 299 * <b>mmirf/plugins/</b> 300 * 301 * If omitted: the default plugin-path is used 302 * (see {@link mmir.Constants#getPluginsPath}) 303 * 304 * @param {Function} [cbFunction] OPTIONAL 305 * The function that should be executed after 306 * the plugins are loaded. If the execution of following 307 * functions is dependent on the present of plugins, they 308 * should be triggered from inside the callback-function 309 * 310 * @returns {Promise} a Deferred.promise (see loadImpl()) 311 * 312 * @async 313 * @public 314 * @memberOf mmir.CommonUtils.prototype 315 */ 316 loadAllCordovaPlugins : function(pluginsPath, cbFunction) { 317 318 if(typeof pluginsPath === 'function'){ 319 cbFunction = pluginsPath; 320 pluginsPath = null; 321 } 322 323 if(typeof pluginsPath !== 'string'){ 324 pluginsPath = constants.getPluginsPath(); 325 } 326 327 // reads all *.js files in /assets/www/mmirf/plugins 328 // and loads them dynamically 329 // IMPORTANT: /assets/www/config/directories.json must be up-to-date! 330 // (it contains the list of JS-files for the plugins) 331 // -> use ANT /build.xml for updating 332 // IMPORTANT: the Java-side implementations of the plugins must be enabled 333 // by corresponding entries in /res/plugins.xml file! 334 335 336 //FIXME: Cordova 2.x mechanism!!! (remove when switching to 3.x ?) 337 window.plugins = window.plugins || {}; 338 339 340 return instance.loadImpl( 341 pluginsPath, 342 false, 343 cbFunction, 344 function isPluginAlreadyLoaded(pluginFileName) { 345 if (window.plugins[pluginFileName.replace(/\.[^.]+$/g, "")]) {//<- regexpr for removing file extension 346 return true; 347 } 348 else { 349 return false; 350 } 351 }, 352 function(status, fileName, msg){ 353 if (status === 'info') { 354 if(logger.isInfo()) logger.info('CommonUtils', 'loadAllCordovaPlugins', 'loaded "'+ fileName + '": ' + msg); 355 } 356 else if (status === 'warning') { 357 if(logger.isWarn()) logger.warn('CommonUtils', 'loadAllCordovaPlugins', 'loading "'+ fileName + '": ' + msg); 358 } 359 else if (status === 'error') { 360 logger.error('CommonUtils', 'loadAllCordovaPlugins', 'loading "'+ fileName + '": ' + msg); 361 } 362 else { 363 logger.error('CommonUtils', 'loadAllCordovaPlugins', status + ' (UNKNOWN STATUS) -> "'+ fileName + '": ' + msg); 364 } 365 } 366 ); 367 }, 368 369 /** 370 * Get the file path/name for a compiled grammar (executable JavaScript grammars). 371 * 372 * @function 373 * @param {String} generatedGrammarsPath Path of the grammars which should be loaded, e.g. <b>gen/grammar/</b> 374 * @param {String} grammarId the ID (e.g. language code) for the grammar 375 * @param {Boolean} [isFileNameOnly] OPTIONAL 376 * if TRUE then only the file name will be returned, otherwise the full path is returned 377 * 378 * @returns {String} file path / name for the compiled grammar 379 * (returns an empty string, if there is no compile grammar for the specified grammar ID) 380 * 381 * @public 382 * @memberOf mmir.CommonUtils.prototype 383 */ 384 getCompiledGrammarPath : function(generatedGrammarsPath, grammarId, isFileNameOnly) { 385 var files = instance.getDirectoryContentsWithFilter(generatedGrammarsPath, "*.js"); 386 if(!files){ 387 return ''; 388 } 389 var f, index, id; 390 for(var i=0,size=files.length; i < size; ++i){ 391 f = files[i]; 392 index = f.lastIndexOf('_'); 393 if (index !== -1) { 394 id = f.substring(0, index); 395 if(id === grammarId){ 396 return isFileNameOnly? files[i] : generatedGrammarsPath + files[i]; 397 } 398 } 399 } 400 return ''; 401 }, 402 /** 403 * Load all compiled grammars (executable JavaScript grammars). 404 * 405 * @function 406 * @param {String} generatedGrammarsPath Path of the grammars which should be loaded, e.g. <b>gen/grammar/</b> 407 * @param {Function} cbFunction The function that should be executed after the plugins are loaded. 408 * If the execution of following functions is dependent on the presence of the grammars, 409 * they should be triggered from inside the callback-function. 410 * @param {Array<String>} [ignoreGrammarIds] OPTIONAL 411 * grammar IDs that should be ignored, i.e. not loaded, even if there is a file available 412 * 413 * @returns {Promise} a Deferred.promise (see loadImpl()) 414 * 415 * @requires mmir.SemanticInterpreter (must be loaded as dependency "semanticInterpreter" at least once before this function is loaded) 416 * 417 * @async 418 * @public 419 * @memberOf mmir.CommonUtils.prototype 420 */ 421 loadCompiledGrammars : function(generatedGrammarsPath, cbFunction, ignoreGrammarIds) { 422 423 return instance.loadImpl( 424 generatedGrammarsPath, 425 false, 426 cbFunction, 427 function isGrammarAlreadyLoaded(grammarFileName) { 428 var i = grammarFileName.lastIndexOf('_'); 429 if (i !== -1) { 430 var id = grammarFileName.substring(0, i); 431 if(ignoreGrammarIds){ 432 for(var p in ignoreGrammarIds){ 433 if(ignoreGrammarIds.hasOwnProperty(p) && ignoreGrammarIds[p] == id){ 434 return true; 435 } 436 } 437 } 438 return require('semanticInterpreter').hasGrammar(id); 439 } else { 440 return false; 441 } 442 }, 443 function loadCompiledGrammarsStatus(status, fileName, msg) { 444 if (status === 'info') { 445 if(logger.isInfo()) logger.info('CommonUtils', 'loadCompiledGrammars', 'loaded "'+ fileName + '": ' + msg); 446 } 447 else if (status === 'warning') { 448 449 //filter "already loaded" warnings for ignored files: 450 if(ignoreGrammarIds && /already loaded/.test(msg)){ 451 for(var p in ignoreGrammarIds){ 452 if(ignoreGrammarIds.hasOwnProperty(p) && fileName.indexOf(ignoreGrammarIds[p]) === 0){ 453 return;/////////////////////// EARLY EXIT //////////////// 454 } 455 } 456 } 457 458 if(logger.isWarn()) logger.warn('CommonUtils', 'loadCompiledGrammars', 'loading "'+ fileName + '": ' + msg); 459 } 460 else if (status === 'error') { 461 logger.error('CommonUtils', 'loadCompiledGrammars', 'loading "' + fileName + '": ' + msg); 462 } 463 else { 464 logger.error('CommonUtils', 'loadCompiledGrammars', status + ' (UNKNOWN STATUS) -> "' + fileName + '": ' + msg); 465 } 466 } 467 ); 468 469 }, 470 471 /** 472 * Load implementation files (i.e. JavaScript files) from a directory (if <tt>librariesPath</tt> is a String) or 473 * or a list of files-names (if <tt>librariesPath</tt> is an Array of Strings). 474 * 475 * 476 * 477 * @function 478 * @param {String|Array<String>} librariesPath 479 * Path (or list of of the plugins which should be loaded, e.g. <b>mmirf/plugins/</b> 480 * NOTE: The (String) path must be an entry in directories.json! 481 * (directories.json is used to generate/"query" the file-list for the path) 482 * 483 * @param {Boolean} isSerial 484 * Set <code>true</code> if the libraries should be loaded serially, i.e. synchronously, that is "one after the other" (later ones may depend on earlier ones). 485 * set <code>false</code> if libraries should be loaded in parallel, i.e. "asychronously" (NOTE in this case, the libraries must not depend on each other). 486 * 487 * NOTE: The loading process as a hole is asynchronous (regardless of parameter <tt>isSerial</tt>), 488 * i.e. loading is completed when <tt>completedCallback()</tt> is invoked, 489 * NOT when this function returns! 490 * 491 * @param {Function} [completedCallback] 492 * The function that should be executed after the libraries are loaded. 493 * If the execution of following functions is dependent on the presence of the libraries, 494 * they should be capsuled inside this callback-function. 495 * @param {Function} [checkIsAlreadyLoadedFunc] 496 * If provided, this function checks (based on the file-name), if the library is already 497 * loaded. 498 * The signature for the callback is <code>checkIsAlreadyLoadedFunc(String fileName) return [true|false]</code>, 499 * i.e. the function may check - based on the file-name - if the library is already loaded. 500 * If the function returns <tt>true</tt>, the library will not be loaded, and loading continues 501 * with the next library-file. 502 * 503 * NOTE: if <tt>isSerial</tt> is <tt>flase</tt>, libraries with lower indices in the list may 504 * still be loading, when later entries are checked with this callback. In consequence, 505 * the "is already loaded"-check may not be accurate, in case parallel loading is 506 * used and the library-list contains "duplicate" entries. 507 * @param {Function} [statusCallback] 508 * If provided, this function is invoked, when a library was loaded loaded (INFO) or an 509 * error occurs. 510 * The signature for the callback is 511 * <code>statusCallback(String statusLevel, String fileName, String message)</code> 512 * where <tt>statusLevel</tt> is one of <tt>info, warning, error</tt>, 513 * <tt>fileName</tt> is the file-name for the library that this status message concerns, and 514 * <tt>message</tt> is a message text with details concerning the status 515 * 516 * @returns {Promise} a Deferred.promise that will be fullfilled when loadImpl() has finished. 517 * 518 * @async 519 * @public 520 * @memberOf mmir.CommonUtils.prototype 521 */ 522 loadImpl: function (librariesPath, isSerial, completedCallback, checkIsAlreadyLoadedFunc, statusCallback){ 523 524 var _defer = $.Deferred(); 525 526 if(completedCallback){ 527 _defer.always(completedCallback); 528 } 529 530 var isPath = true;//TODO use this for creating absolute paths (-> in case librariesPath is an Array)! 531 var theFileList; 532 if(typeof librariesPath === 'string'){ 533 theFileList = instance.getDirectoryContentsWithFilter(librariesPath, "*.js"); 534 } 535 else { 536 isPath = false; 537 theFileList = librariesPath; 538 librariesPath = ''; 539 } 540 541 var size = theFileList.length; 542 var progress = 0; 543 544 var doLoadImplFile = function doLoadImplFile(fileList, index){ 545 546 if( ! index){ 547 index = 0; 548 } 549 550 var fileName = fileList[index]; 551 552 if ( checkIsAlreadyLoadedFunc && checkIsAlreadyLoadedFunc(fileName) ){ 553 554 if(statusCallback){ 555 statusCallback('warning', fileName, 'already loaded ' + librariesPath+fileName); 556 } 557 558 ++progress; 559 //synchronous load: load next recursively 560 if(isSerial){ 561 doLoadImplFile(fileList, index+1); 562 } 563 564 } else { 565 566 //handler that is invoked after file has been loaded: 567 var handleScriptDone = function(){ 568 //"notify" that this file has been DONE: 569 ++progress; 570 571 //check: are all entries of the list done? 572 if (progress < size){ 573 574 if( isSerial ){ 575 //synchronous load: load next entry recursively, when previous, i.e. this, one has finished: 576 doLoadImplFile(fileList, index+1); 577 } 578 //all entries already have been processed -> stop now. 579 return; 580 } 581 582 //ASSERT: all entries of the file-list are DONE -> triggere completedCallback 583 584 // if (typeof completedCallback == 'function'){ 585 // completedCallback(); 586 // } else { 587 // if(statusCallback){ 588 // statusCallback('warning', fileName, 'provided callback for COMPLETION is not a function: '+completedCallback); 589 // } 590 // else { 591 // logger.warn('[loadImpl] callback for COMPLETION is not a function: '+completedCallback); 592 // } 593 // } 594 _defer.resolve(); 595 }; 596 597 /// ATTENTION: $.getScript --> mobileDS.CommonUtils.getInstance().getLocalScript 598 /// under Android 4.0 getScript is not wokring properly 599 instance.getLocalScript(librariesPath+fileName, 600 function(){ 601 602 if(statusCallback){ 603 statusCallback('info', fileName, 'done loading ' + librariesPath+fileName); 604 } 605 606 handleScriptDone(); 607 }, 608 function(exception) { 609 if(statusCallback){ 610 statusCallback('error', fileName, 'could not load "' + librariesPath+fileName + '": ' + exception); 611 } 612 else { 613 // print out an error message 614 logger.error('[loadImpl] Could not load "' + librariesPath+fileName + '": ', exception); 615 } 616 617 //NOTE: in case of an error, will still try to load the other files from the list: 618 619 handleScriptDone(); 620 } 621 );//END: getLocalScript(callbacks) 622 } 623 };//END: doLoadImplFile(name,index) 624 625 if(logger.isVerbose()) logger.verbose('about to load all libraries from path "'+librariesPath+'"...'); 626 627 if(size < 1){ 628 //if there are no files to resolve: 629 // immediately resolve Promise / trigger callback 630 _defer.resolve(); 631 } 632 else if( ! isSerial){ 633 //asynchronous load: trigger loading for all at once: 634 for(var counter=0; counter < size; ++counter){ 635 doLoadImplFile(theFileList, counter); 636 } 637 } 638 else { 639 //synchronous load: start with first (the next one will be loaded recursively, when the first one was loaded) 640 doLoadImplFile(theFileList); 641 } 642 643 return _defer.promise(); 644 }, 645 646 /** 647 * Detects via the user-agent-string if the application is running 648 * on Android. 649 * 650 * @function 651 * @public 652 * @returns {Boolean} <b>True</b> if application is running on 653 * Android, <b>False</b> otherwise 654 * 655 * @memberOf mmir.CommonUtils.prototype 656 */ 657 isRunningOnAndroid : function() { 658 // Testing if user-Agent-/ or appVersion-String contains 'android' 659 if ((navigator.userAgent.toLowerCase().indexOf("android") > -1) 660 || (navigator.appVersion.toLowerCase().indexOf("android") > -1)) { 661 662 return true; 663 } 664 else { 665 return false; 666 } 667 }, 668 669 /** 670 * Should detect - via the user-agent-string - if the application is 671 * running on Android, Symbian or iOS; in other words: on a 672 * smartphone. 673 * 674 * @function 675 * @public 676 * @returns {Boolean} <b>True</b> if application is running on 677 * smartphone, <b>False</b> otherwise 678 * 679 * @memberOf mmir.CommonUtils.prototype 680 */ 681 isRunningOnSmartphone : function() { 682 // Testing if user-Agent-/ or appVersion-String contains 683 // 'Android' or 'iOS' 684 // at the moment only Android-, iOS and Symbian-strings are 'implemented' 685 var testString = navigator.userAgent.toLowerCase() 686 + navigator.appVersion.toLowerCase(); 687 688 if ((testString.indexOf("android") > -1) 689 || (testString.indexOf("ios") > -1) 690 || (testString.indexOf("symbian") > -1)) { 691 692 return true; 693 } 694 else { 695 return false; 696 } 697 }, 698 699 /** 700 * <div class="box important"> <b>Note:</b> On Android 4.0 701 * jQuery.getScript() is not working properly - so use this function instead! 702 * </div> 703 * 704 * Similar to the jQuery.getScript() function - appending a url of a 705 * javascript-source to the header of the main document.<br> 706 * This function also calls a success-callback if the script was 707 * successfully loaded or a fail-callback.<br> 708 * 709 * 710 * @function 711 * @param {String} 712 * scriptUrl source of javascript-file 713 * @param {Function} 714 * success success callback function 715 * @param {Function} 716 * fail fail callback function 717 * @async 718 * @public 719 * @memberOf mmir.CommonUtils.prototype 720 */ 721 getLocalScript : function(scriptUrl, success, fail) { 722 var head = document.getElementsByTagName('head')[0]; 723 var script = document.createElement('script'); 724 script.type = 'text/javascript'; 725 script.src = scriptUrl; 726 script.onload = function() { 727 if(success){ 728 success.apply(this, arguments); 729 } 730 }; 731 script.onerror = function(e) { 732 if(fail){ 733 fail.apply(this, arguments); 734 } 735 else { 736 logger.error('CommonUtils', 'getLocalScript', 'Loading Script Failed from "' + scriptUrl + '"', e); 737 } 738 }; 739 head.appendChild(script); 740 }, 741 742 /** 743 * This function returns an array of strings with the contents of a 744 * directory. 745 * 746 * @function 747 * @param {String} 748 * pathname Path of the directory which contents should 749 * be returned 750 * @public 751 * @returns {Array} Array of Strings which contains the contents of 752 * the directory 753 * 754 * @memberOf mmir.CommonUtils.prototype 755 */ 756 getDirectoryContents : function(pathname) { 757 var retValue; 758 759 pathname = this.stripPathName(pathname); 760 761 try { 762 retValue = this.directoryStructure[pathname]; 763 } catch (e) { 764 logger.error(e); 765 retValue = null; 766 } 767 return retValue; 768 }, 769 770 /** 771 * This function returns an array of strings with the contents of a 772 * directory, giving only those files which match the filter. 773 * 774 * @function 775 * @param {String} pathname 776 * Path of the directory which's contents should be 777 * returned 778 * @param {String} filter 779 * Filter for file-names which may contain a wildcard, 780 * e.g.: <b>*.js</b>, <b>*</b> or <b>*.ehtml</b> 781 * @public 782 * @returns {Array} Array of Strings which contains the contents of 783 * the directory. 784 * Or <code>null</code>, if no matching contents could be 785 * found. 786 * 787 * @memberOf mmir.CommonUtils.prototype 788 */ 789 getDirectoryContentsWithFilter : function(pathname, filter) { 790 791 var retValue = []; 792 793 var tmpfilter = '^' + filter.replace('.', '\\.').replace('*', '.*').replace('\$', '\\$') + '$'; // e.g.,// '^.*\.js$' 794 795 var filterRegExp = new RegExp(tmpfilter, 'gi'); 796 797 pathname = this.stripPathName(pathname); 798 799 try { 800 var tmp = this.directoryStructure[pathname]; 801 if (typeof tmp === 'undefined') { 802 if(logger.isInfo()) logger.info('CommonUtils', 'getDirectoryContentsWithFilter', '[' + pathname + ' | ' + filter + '] not found.'); 803 retValue = null; 804 } 805 else { 806 for (var i = 0; i < tmp.length; i++) { 807 if (tmp[i].match(filterRegExp)) { 808 retValue.push(tmp[i]); 809 } 810 } 811 } 812 } catch (e) { 813 logger.error('CommonUtils', 'getDirectoryContentsWithFilter', '[' + pathname + ' | ' + filter + '] ', e); 814 retValue = null; 815 } 816 return retValue; 817 }, 818 819 /** 820 * Checks if an object is an <code>Array</code>. 821 * 822 * <p> 823 * This function can be savely run in arbirtray contexts, e.g. 824 * 825 * <pre> 826 * var checkArray = mmir.CommonUtils.getInstance().isArray; 827 * if( checkArray(someObject) ){ 828 * ... 829 * </pre> 830 * 831 * @function 832 * @param {Object} 833 * object the Object for checking if it is an Array 834 * @public 835 * @returns {Boolean} <code>true</code> if <code>object</code> 836 * is an <code>Array</code>, otherwise 837 * <code>false</code>. 838 * 839 * @memberOf mmir.CommonUtils.prototype 840 */ 841 isArray : function(object) { 842 return isArrayHelper(object); 843 }, 844 845 /** 846 * This function iterates over all elements of a specific class and 847 * changes the font-size of the contained text to the maximal 848 * possible size - while still being small enough to fit in the 849 * element. 850 * 851 * @function 852 * @param {String} 853 * class_name Name of the class which inner text should 854 * be fitted to the size of the element 855 * 856 * @requires jQuery 857 * @public 858 * @memberOf mmir.CommonUtils.prototype 859 */ 860 resizeFitToSourroundingBox : function(class_name) { 861 // resize the font in box_fit-class, so that it won't overlap its div-box 862 $(function() { 863 864 var smallest_font = 1000; 865 $(class_name).each(function(i, box) { 866 var width = $( box ).width(), 867 html = '<span style="white-space:nowrap">', 868 line = $( box ).wrapInner( html ).children()[ 0 ], 869 n = parseInt($( box ).css("font-size"), 10); 870 871 $( box ).css( 'font-size', n ); 872 873 while ( $( line ).width() > width ) { 874 $( box ).css( 'font-size', --n ); 875 } 876 877 $( box ).text( $( line ).text() ); 878 879 n = parseInt($( box ).css("font-size"), 10); 880 881 if (n < smallest_font) { 882 smallest_font = n; 883 } 884 }); 885 886 $(class_name).each(function(i, box) { 887 $(box).css('font-size', smallest_font); 888 }); 889 }); 890 }, 891 892 /** 893 * Converts the object to a valid JSON String value. 894 * 895 * Ensures that the returned value does not contain (un-escaped) 896 * double-quotes, so that the returned value can be used as a JSON 897 * value, e.g. </br> 898 * 899 * @function 900 * @param {Object} 901 * theObjectValue the object to convert to a JSON String 902 * value. If NULL or UNDEFINED, an EMPTY String will be 903 * returned 904 * @returns {String} the String value 905 * @public 906 * @memberOf mmir.CommonUtils.prototype 907 * 908 * @example 909 * var jsonValue = mmir.CommonUtils.toJSONStringValue(someValue); 910 * var data = JSON.parse('"theValue":"' + jsonValue + '"'); 911 */ 912 toJSONStringValue : function(theObjectValue) { 913 if (typeof theObjectValue !== 'undefined' && theObjectValue !== null) { 914 if (typeof theObjectValue !== 'string') { 915 theObjectValue = theObjectValue.toString(); 916 } 917 theObjectValue = theObjectValue.escapeDoubleQuotes(); 918 } 919 else { 920 theObjectValue = ''; 921 } 922 return theObjectValue; 923 }, 924 925 /** 926 * Converts the object to a valid JSON String value. 927 * 928 * Ensures that the returned value does not contain (un-escaped) 929 * double-quotes, so that the returned value can be used as a JSON 930 * value, also does replace all newlines with the HTML-equivalent 931 * '<br/>', e.g. 932 * 933 * @function 934 * @param {Object} 935 * theObjectValue the object to convert to a JSON String 936 * value. If NULL or UNDEFINED, an EMPTY String will be 937 * returned 938 * @returns {String} the String value 939 * @public 940 * @memberOf mmir.CommonUtils.prototype 941 * 942 * @example 943 * var jsonValue = mmir.CommonUtils.convertJSONStringValueToHTML(someValue); 944 * var data = JSON.parse('"theValue":"' + jsonValue + '"'); 945 * ... 946 */ 947 convertJSONStringValueToHTML : function(str) { 948 if (typeof str !== 'undefined' && str !== null) { 949 950 if (typeof str !== 'string') { 951 str = str.toString(); 952 } 953 // escape double-quotes, if necessary 954 // replace (all variants of) newlines with HTML-newlines 955 str = str.escapeDoubleQuotes().replaceAll('\r\n', '<br/>') 956 .replaceAll('\n', '<br/>') 957 .replaceAll('\r', '<br/>'); 958 } else { 959 str = ''; 960 } 961 return str; 962 963 }, 964 965 /** 966 * Converts the object's direct properties to a valid JSON String 967 * (i.e. no recursion for Object properties). 968 * 969 * @function 970 * @param {Object} 971 * _o the object to convert to a JSON String. 972 * @returns {String} the String value 973 * @public 974 * @memberOf mmir.CommonUtils.prototype 975 */ 976 convertJSONStringToHTML : function(_o) { 977 // var parse = function(_o){ 978 var a = new Array(), t; 979 for ( var p in _o) { 980 if (_o.hasOwnProperty(p)) { 981 t = _o[p]; 982 if (t != null) { 983 if (t && typeof t == "object") { 984 a[a.length] = p + ":{ " + arguments.callee(t).join(", ") + "}"; 985 } 986 else { 987 if (typeof t == "string") { 988 a[a.length] = [ p + ": \"" + t.toString() + "\"" ]; 989 } 990 else { 991 a[a.length] = [ p + ": " + t.toString() ]; 992 } 993 } 994 } 995 } 996 } 997 // return a; 998 // }; 999 // return "{" + parse(o).join(", ") + "}"; 1000 1001 return "{" + a.join(", ") + "}"; 1002 }, 1003 1004 /** 1005 * 1006 * IMPORTED FROM paramsParseFunc.js 1007 * <p> 1008 * 1009 * Convert parameter-part of an URL to a "dictionary", containing 1010 * the parameter keys and values 1011 * <p> 1012 * 1013 * <code>?id=5&name=heinz&name=kunz</code> → 1014 * <pre> 1015 * { 1016 * id: "5", 1017 * name: ["heinz", "kunz"], 1018 * 1019 * //utility functions 1020 * has: function(key) : Boolean, 1021 * isMultiple: function(key) : Boolean,// is entry an Array of values 1022 * getKeys: function() : String[], // get list of all keys 1023 * } 1024 * </pre> 1025 * <p> 1026 * 1027 * The returnd "dictionary" has the following functions: 1028 * <ul> 1029 * <li>has(String key): returns <code>true</code> if the 1030 * dictionary contains an entry for <code>key</code></li> 1031 * <li>isMultiple(String key): returns <code>true</code> if the 1032 * entry for <code>key</code> is an Array (i.e. the URL contained 1033 * multiple values for this key)</li> 1034 * <li>getKeys(): returns an Array with the keys (String) for all 1035 * entries</li> 1036 * </ul> 1037 * 1038 * @function 1039 * @param {String} urlParamsPartStrings 1040 * the parameter-part of the URL, i.e. <code>&...</code> 1041 * @return {Object} a "dictionary" for the parameters 1042 * @public 1043 * @memberOf mmir.CommonUtils.prototype 1044 */ 1045 parseParamsToDictionary : paramsParseFunc, 1046 1047 /** 1048 * This function is used check whether a network connection is 1049 * enabled. </br> This version of checking the network connection is 1050 * based on the cordova 2.3.0 API. 1051 * 1052 * TODO implement with HTML5 functions (in addition to / instead of 1053 * cordova)? 1054 * 1055 * @requires Cordova: org.apache.cordova.network-information 1056 * 1057 * @function 1058 * @public 1059 * @returns {Boolean} <code>true</code> if a network connection is enabled 1060 * 1061 * @memberOf mmir.CommonUtils.prototype 1062 */ 1063 checkNetworkConnection : function() { 1064 1065 if(logger.isVerbose()) logger.verbose("Checking network status..."); 1066 1067 if(typeof navigator === 'undefined'){ 1068 logger.error('Cannot check network status: navigator object is not available!'); 1069 return 'UNKNOWN'; 1070 } 1071 1072 //ASSERT: navigator exists 1073 1074 if(!navigator.connection){ 1075 if(logger.isInfo()) logger.warn('Cannot check network status: object navigator.connection is not available'); 1076 if(typeof navigator.onLine !== 'undefined'){ 1077 return navigator.onLine; 1078 } 1079 else { 1080 return 'UNKNOWN'; 1081 } 1082 } 1083 var networkState = navigator.connection.type; 1084 1085 //TODO make states-obj a 'private' field of CommonUtils 1086 var states = {}; 1087 states[Connection.UNKNOWN] = 'Unknown connection'; 1088 states[Connection.ETHERNET] = 'Ethernet connection'; 1089 states[Connection.WIFI] = 'WiFi connection'; 1090 states[Connection.CELL_2G] = 'Cell 2G connection'; 1091 states[Connection.CELL_3G] = 'Cell 3G connection'; 1092 states[Connection.CELL_4G] = 'Cell 4G connection'; 1093 states[Connection.CELL] = 'Cell generic connection'; 1094 states[Connection.NONE] = 'No network connection'; 1095 1096 if (Connection.NONE === networkState){ 1097 //alert('Connection type: ' + states[networkState]); 1098 return false; 1099 } 1100 return true; 1101 }, 1102 1103 /** 1104 * Parses the directory structure - paths given by property {@link mmir.CommonUtils-constructor-directoriesToParse} - and storing the result in the class-property {@link mmir.CommonUtils-directoryStructure} 1105 * 1106 * @function 1107 * @param {Function} [success] The function that should be executed after the diretories are parsed - it's best to include all following functions inside the callback-function. 1108 * @param {Function} [errorFunc] callback function that is invoked if an error occured during initialization. 1109 * @async 1110 * @public 1111 * @memberOf mmir.CommonUtils.prototype 1112 */ 1113 loadDirectoryStructure: function (success, errorFunc) { 1114 var _defer = $.Deferred(); 1115 var self = this; 1116 1117 if(success){ 1118 _defer.done(success); 1119 } 1120 if(errorFunc){ 1121 _defer.fail(errorFunc); 1122 } 1123 1124 var directoryFileUrl = constants.getDirectoriesFileUrl(); 1125 1126 //load configuration file asynchronously: 1127 $.ajax({ 1128 async: true, 1129 dataType: "json", 1130 url: directoryFileUrl, 1131 success: function(data){ 1132 if(logger.isVerbose()) logger.verbose("DirectoryListing.getDirectoryStructure: loaded file from "+directoryFileUrl); 1133 1134 if(data){ 1135 if(logger.isVerbose()) logger.verbose("DirectoryListing.getDirectoryStructure: Succeeded to load directory structure from '"+directoryFileUrl+"'! Data: "+ JSON.stringify(data)); 1136 1137 self.directoryStructure = data; 1138 1139 if(logger.isVerbose()) logger.verbose("[getDirectoryStructure] finished.");//debug 1140 1141 _defer.resolve(self); 1142 } 1143 }, 1144 error: function(jqXHR, textStatus, errorThrown){ 1145 if(logger.isVerbose()) logger.verbose("DirectoryListing.getDirectoryStructure: failed to load file from '"+directoryFileUrl+"'! Status "+textStatus+": "+ errorThrown+ ", "+JSON.stringify(jqXHR)); 1146 1147 var msg = "[ERROR] " + textStatus+": failed to load file from '"+directoryFileUrl+"' - "+ errorThrown; 1148 if( ! errorFunc){ 1149 logger.error('CommonUtils', 'loadDirectoryStructure', msg); 1150 } 1151 1152 _defer.fail(msg); 1153 } 1154 }); 1155 1156 return _defer.promise(); 1157 }, 1158 1159 init: function(success, errorFunc){ 1160 1161 var initPromise; 1162 1163 //use the Deferred from load-dir-struct, since this is the only async initialization atm: 1164 initPromise = this.loadDirectoryStructure.apply(this, arguments); 1165 1166 //replace init so that we do not ivoke load-dir-struct multiple times 1167 this.__initDeferred = initPromise; 1168 this.init = function initCompleted(onsuccess, onerror){ 1169 if(onsuccess){ 1170 this.__initDeferred.done(success); 1171 } 1172 if(onerror){ 1173 this.__initDeferred.fail(errorFunc); 1174 } 1175 return this.__initDeferred; 1176 }; 1177 1178 return initPromise; 1179 } 1180 1181 /** 1182 * Set to "backwards compatibility mode" (for pre version 2.0). 1183 * 1184 * This function re-adds deprecated and removed functions and 1185 * properties to the CommonUtils instance. 1186 * 1187 * NOTE that once set to compatibility mode, it cannot be reset to 1188 * non-compatibility mode. 1189 * 1190 * @deprecated use only for backward compatibility 1191 * 1192 * @async 1193 * @requires jQuery.Deferred 1194 * @requires mmir.CommonUtils.setToCompatibilityModeExtension 1195 * 1196 * @param {Function} [success] 1197 * a callback function that is invoked, after compatibility mode 1198 * was set (alternatively the returned promise can be used). 1199 * @returns {jQuery.Promise} 1200 * a Deffered.promise that is resolved, after compatibility mode 1201 * was set 1202 * 1203 * @memberOf mmir.CommonUtils.prototype 1204 * 1205 * @see mmir.CommonUtils.setToCompatibilityModeExtension 1206 * 1207 */ 1208 , setToCompatibilityMode : function(success) { 1209 1210 var defer = $.Deferred(); 1211 if(success){ 1212 defer.always(success); 1213 } 1214 1215 require(['commonUtilsCompatibility'],function(setCompatibility){ 1216 1217 setCompatibility(instance); 1218 1219 defer.resolve(); 1220 }); 1221 1222 return defer.promise(); 1223 } 1224 1225 };// END: return {... 1226 1227 }// END: constructor() 1228 1229 1230 instance = new constructor($, constants); 1231 1232 return instance; 1233 1234 1235 1236 }); 1237