1 /* Licensed to the Apache Software Foundation (ASF) under one or more 2 * contributor license agreements. See the NOTICE file distributed with 3 * this work for additional information regarding copyright ownership. 4 * The ASF licenses this file to you under the Apache License, Version 2.0 5 * (the "License"); you may not use this file except in compliance with 6 * the License. You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 /* 17 theoretically we could save some code 18 by 19 defining the parent object as 20 var parent = new Object(); 21 parent.prototype = new myfaces._impl.core._Runtime(); 22 extendClass(function () { 23 }, parent , { 24 But for now we are not doing it the little bit of saved 25 space is not worth the loss of readability 26 */ 27 /** 28 * @memberOf myfaces._impl 29 * @namespace 30 * @name _util 31 */ 32 /** 33 * @class 34 * @name _Lang 35 * @memberOf myfaces._impl._util 36 * @extends myfaces._impl.core._Runtime 37 * @namespace 38 * @description Object singleton for Language related methods, this object singleton 39 * decorates the namespace myfaces._impl.core._Runtime and adds a bunch of new methods to 40 * what _Runtime provided 41 * */ 42 _MF_SINGLTN(_PFX_UTIL + "_Lang", Object, /** @lends myfaces._impl._util._Lang.prototype */ { 43 _processedExceptions:{}, 44 _installedLocale:null, 45 _RT:myfaces._impl.core._Runtime, 46 /** 47 * returns a given localized message upon a given key 48 * basic java log like templating functionality is included 49 * 50 * @param {String} key the key for the message 51 * @param {String} defaultMessage optional default message if none was found 52 * 53 * Additionally you can pass additional arguments, which are used 54 * in the same way java log templates use the params 55 * 56 * @param key 57 */ 58 getMessage:function (key, defaultMessage /*,vararg templateParams*/) { 59 if (!this._installedLocale) { 60 //we first try to install language and variant, if that one fails 61 //we try to install the language only, and if that one fails 62 //we install the base messages 63 this.initLocale(); 64 } 65 var msg = this._installedLocale[key] || defaultMessage || key + " - undefined message"; 66 //we now make a simple templating replace of {0}, {1} etc... with their corresponding 67 //arguments 68 for (var cnt = 2; cnt < arguments.length; cnt++) { 69 msg = msg.replace(new RegExp(["\\{", cnt - 2, "\\}"].join(""), "g"), new String(arguments[cnt])); 70 } 71 return msg; 72 }, 73 /** 74 * (re)inits the currently installed 75 * messages so that after loading the main scripts 76 * a new locale can be installed optionally 77 * to our i18n subsystem 78 * 79 * @param newLocale locale override 80 */ 81 initLocale:function (newLocale) { 82 if (newLocale) { 83 this._installedLocale = new newLocale(); 84 return; 85 } 86 var language_Variant = this._RT.getLanguage(this._RT.getGlobalConfig("locale")), 87 langStr = language_Variant ? language_Variant.language : "", 88 variantStr = language_Variant ? [language_Variant.language, "_", language_Variant.variant || ""].join("") : "", 89 i18nRoot = myfaces._impl.i18n, i18nHolder = i18nRoot["Messages_" + variantStr] || i18nRoot["Messages_" + langStr] || i18nRoot["Messages"]; 90 this._installedLocale = new i18nHolder(); 91 }, 92 assertType:function (probe, theType) { 93 return this._RT.assertType(probe, theType); 94 }, 95 exists:function (nms, theType) { 96 return this._RT.exists(nms, theType); 97 }, 98 fetchNamespace:function (namespace) { 99 this._assertStr(namespace, "fetchNamespace", "namespace"); 100 return this._RT.fetchNamespace(namespace); 101 }, 102 reserveNamespace:function (namespace) { 103 this._assertStr(namespace, "reserveNamespace", "namespace"); 104 return this._RT.reserveNamespace(namespace); 105 }, 106 globalEval:function (code) { 107 this._assertStr(code, "globalEval", "code"); 108 return this._RT.globalEval(code); 109 }, 110 /** 111 * determines the correct event depending 112 * on the browsers state 113 * 114 * @param evt incoming event object (note not all browsers 115 * have this) 116 * 117 * @return an event object no matter what is incoming 118 */ 119 getEvent:function (evt) { 120 evt = (!evt) ? window.event || {} : evt; 121 return evt; 122 }, 123 /** 124 * cross port from the dojo lib 125 * browser save event resolution 126 * @param evt the event object 127 * (with a fallback for ie events if none is present) 128 */ 129 getEventTarget:function (evt) { 130 //ie6 and 7 fallback 131 evt = this.getEvent(evt); 132 /** 133 * evt source is defined in the jsf events 134 * seems like some component authors use our code 135 * so we add it here see also 136 * https://issues.apache.org/jira/browse/MYFACES-2458 137 * not entirely a bug but makes sense to add this 138 * behavior. I dont use it that way but nevertheless it 139 * does not break anything so why not 140 * */ 141 var t = evt.srcElement || evt.target || evt.source || null; 142 while ((t) && (t.nodeType != 1)) { 143 t = t.parentNode; 144 } 145 return t; 146 }, 147 148 /** 149 * equalsIgnoreCase, case insensitive comparison of two strings 150 * 151 * @param source 152 * @param destination 153 */ 154 equalsIgnoreCase:function (source, destination) { 155 //either both are not set or null 156 if (!source && !destination) { 157 return true; 158 } 159 //source or dest is set while the other is not 160 if (!source || !destination) return false; 161 //in any other case we do a strong string comparison 162 return source.toLowerCase() === destination.toLowerCase(); 163 }, 164 165 /** 166 * Save document.getElementById (this code was ported over from dojo) 167 * the idea is that either a string or domNode can be passed 168 * @param {Object} reference the reference which has to be byIded 169 */ 170 byId:function (/*object*/ reference) { 171 if (!reference) { 172 throw this.makeException(new Error(), null, null, this._nameSpace, "byId", this.getMessage("ERR_REF_OR_ID", null, "_Lang.byId", "reference")); 173 } 174 return (this.isString(reference)) ? document.getElementById(reference) : reference; 175 }, 176 177 /** 178 * String to array function performs a string to array transformation 179 * @param {String} it the string which has to be changed into an array 180 * @param {RegExp} splitter our splitter reglar expression 181 * @return an array of the splitted string 182 */ 183 strToArray:function (/*string*/ it, /*regexp*/ splitter) { 184 // summary: 185 // Return true if it is a String 186 this._assertStr(it, "strToArray", "it"); 187 if (!splitter) { 188 throw this.makeException(new Error(), null, null, this._nameSpace, "strToArray", this.getMessage("ERR_PARAM_STR_RE", null, "myfaces._impl._util._Lang.strToArray", "splitter")); 189 } 190 var retArr = it.split(splitter); 191 var len = retArr.length; 192 for (var cnt = 0; cnt < len; cnt++) { 193 retArr[cnt] = this.trim(retArr[cnt]); 194 } 195 return retArr; 196 }, 197 _assertStr:function (it, functionName, paramName) { 198 if (!this.isString(it)) { 199 throw this.makeException(new Error(), null, null, this._nameSpace, arguments.caller.toString(), this.getMessage("ERR_PARAM_STR", null, "myfaces._impl._util._Lang." + functionName, paramName)); 200 } 201 }, 202 /** 203 * hyperfast trim 204 * http://blog.stevenlevithan.com/archives/faster-trim-javascript 205 * crossported from dojo 206 */ 207 trim:function (/*string*/ str) { 208 this._assertStr(str, "trim", "str"); 209 str = str.replace(/^\s\s*/, ''); 210 var ws = /\s/, i = str.length; 211 212 while (ws.test(str.charAt(--i))) { 213 //do nothing 214 } 215 return str.slice(0, i + 1); 216 }, 217 /** 218 * Backported from dojo 219 * a failsafe string determination method 220 * (since in javascript String != "" typeof alone fails!) 221 * @param it {|Object|} the object to be checked for being a string 222 * @return true in case of being a string false otherwise 223 */ 224 isString:function (/*anything*/ it) { 225 // summary: 226 // Return true if it is a String 227 return !!arguments.length && it != null && (typeof it == "string" || it instanceof String); // Boolean 228 }, 229 /** 230 * hitch backported from dojo 231 * hitch allows to assign a function to a dedicated scope 232 * this is helpful in situations when function reassignments 233 * can happen 234 * (notably happens often in lazy xhr code) 235 * 236 * @param {Function} scope of the function to be executed in 237 * @param {Function} method to be executed, the method must be of type function 238 * 239 * @return whatever the executed method returns 240 */ 241 hitch:function (scope, method) { 242 return !scope ? method : function () { 243 return method.apply(scope, arguments || []); 244 }; // Function 245 }, 246 /** 247 * Helper function to merge two maps 248 * into one 249 * @param {Object} dest the destination map 250 * @param {Object} src the source map 251 * @param {boolean} overwrite if set to true the destination is overwritten if the keys exist in both maps 252 **/ 253 mixMaps:function (dest, src, overwrite, blockFilter, whitelistFilter) { 254 if (!dest || !src) { 255 throw this.makeException(new Error(), null, null, this._nameSpace, "mixMaps", this.getMessage("ERR_PARAM_MIXMAPS", null, "_Lang.mixMaps")); 256 } 257 var _undef = "undefined"; 258 for (var key in src) { 259 if (!src.hasOwnProperty(key)) continue; 260 if (blockFilter && blockFilter[key]) { 261 continue; 262 } 263 if (whitelistFilter && !whitelistFilter[key]) { 264 continue; 265 } 266 if (!overwrite) { 267 /** 268 *we use exists instead of booleans because we cannot rely 269 *on all values being non boolean, we would need an elvis 270 *operator in javascript to shorten this :-( 271 */ 272 dest[key] = (_undef != typeof dest[key]) ? dest[key] : src[key]; 273 } else { 274 dest[key] = (_undef != typeof src[key]) ? src[key] : dest[key]; 275 } 276 } 277 return dest; 278 }, 279 /** 280 * checks if an array contains an element 281 * @param {Array} arr array 282 * @param {String} str string to check for 283 */ 284 contains:function (arr, str) { 285 if (!arr || !str) { 286 throw this.makeException(new Error(), null, null, this._nameSpace, "contains", this.getMessage("ERR_MUST_BE_PROVIDED", null, "_Lang.contains", "arr {array}", "str {string}")); 287 } 288 return this.arrIndexOf(arr, str) != -1; 289 }, 290 arrToMap:function (arr, offset) { 291 var ret = new Array(arr.length); 292 var len = arr.length; 293 offset = (offset) ? offset : 0; 294 for (var cnt = 0; cnt < len; cnt++) { 295 ret[arr[cnt]] = cnt + offset; 296 } 297 return ret; 298 }, 299 objToArray:function (obj, offset, pack) { 300 if (!obj) { 301 return null; 302 } 303 //since offset is numeric we cannot use the shortcut due to 0 being false 304 //special condition array delivered no offset no pack 305 if (obj instanceof Array && !offset && !pack) return obj; 306 var finalOffset = ('undefined' != typeof offset || null != offset) ? offset : 0; 307 var finalPack = pack || []; 308 try { 309 return finalPack.concat(Array.prototype.slice.call(obj, finalOffset)); 310 } catch (e) { 311 //ie8 (again as only browser) delivers for css 3 selectors a non convertible object 312 //we have to do it the hard way 313 //ie8 seems generally a little bit strange in its behavior some 314 //objects break the function is everything methodology of javascript 315 //and do not implement apply call, or are pseudo arrays which cannot 316 //be sliced 317 for (var cnt = finalOffset; cnt < obj.length; cnt++) { 318 finalPack.push(obj[cnt]); 319 } 320 return finalPack; 321 } 322 }, 323 /** 324 * foreach implementation utilizing the 325 * ECMAScript wherever possible 326 * with added functionality 327 * 328 * @param arr the array to filter 329 * @param func the closure to apply the function to, with the syntax defined by the ecmascript functionality 330 * function (element<,key, array>) 331 * <p /> 332 * optional params 333 * <p /> 334 * <ul> 335 * <li>param startPos (optional) the starting position </li> 336 * <li>param scope (optional) the scope to apply the closure to </li> 337 * </ul> 338 */ 339 arrForEach:function (arr, func /*startPos, scope*/) { 340 if (!arr || !arr.length) return; 341 var startPos = Number(arguments[2]) || 0; 342 var thisObj = arguments[3]; 343 //check for an existing foreach mapping on array prototypes 344 //IE9 still does not pass array objects as result for dom ops 345 arr = this.objToArray(arr); 346 (startPos) ? arr.slice(startPos).forEach(func, thisObj) : arr.forEach(func, thisObj); 347 }, 348 /** 349 * foreach implementation utilizing the 350 * ECMAScript wherever possible 351 * with added functionality 352 * 353 * @param arr the array to filter 354 * @param func the closure to apply the function to, with the syntax defined by the ecmascript functionality 355 * function (element<,key, array>) 356 * <p /> 357 * additional params 358 * <ul> 359 * <li> startPos (optional) the starting position</li> 360 * <li> scope (optional) the scope to apply the closure to</li> 361 * </ul> 362 */ 363 arrFilter:function (arr, func /*startPos, scope*/) { 364 if (!arr || !arr.length) return []; 365 arr = this.objToArray(arr); 366 return ((startPos) ? arr.slice(startPos).filter(func, thisObj) : arr.filter(func, thisObj)); 367 }, 368 /** 369 * adds a EcmaScript optimized indexOf to our mix, 370 * checks for the presence of an indexOf functionality 371 * and applies it, otherwise uses a fallback to the hold 372 * loop method to determine the index 373 * 374 * @param arr the array 375 * @param element the index to search for 376 */ 377 arrIndexOf:function (arr, element /*fromIndex*/) { 378 if (!arr || !arr.length) return -1; 379 var pos = Number(arguments[2]) || 0; 380 arr = this.objToArray(arr); 381 return arr.indexOf(element, pos); 382 }, 383 /** 384 * helper to automatically apply a delivered arguments map or array 385 * to its destination which has a field "_"<key> and a full field 386 * 387 * @param dest the destination object 388 * @param args the arguments array or map 389 * @param argNames the argument names to be transferred 390 */ 391 applyArgs:function (dest, args, argNames) { 392 var UDEF = 'undefined'; 393 if (argNames) { 394 for (var cnt = 0; cnt < args.length; cnt++) { 395 //dest can be null or 0 hence no shortcut 396 if (UDEF != typeof dest["_" + argNames[cnt]]) { 397 dest["_" + argNames[cnt]] = args[cnt]; 398 } 399 if (UDEF != typeof dest[ argNames[cnt]]) { 400 dest[argNames[cnt]] = args[cnt]; 401 } 402 } 403 } else { 404 for (var key in args) { 405 if (!args.hasOwnProperty(key)) continue; 406 if (UDEF != typeof dest["_" + key]) { 407 dest["_" + key] = args[key]; 408 } 409 if (UDEF != typeof dest[key]) { 410 dest[key] = args[key]; 411 } 412 } 413 } 414 }, 415 416 /** 417 * transforms a key value pair into a string 418 * @param key the key 419 * @param val the value 420 * @param delimiter the delimiter 421 */ 422 keyValToStr:function (key, val, delimiter) { 423 var ret = [], pushRet = this.hitch(ret, ret.push); 424 pushRet(key); 425 pushRet(val); 426 delimiter = delimiter || "\n"; 427 pushRet(delimiter); 428 return ret.join(""); 429 }, 430 parseXML:function (txt) { 431 try { 432 var parser = new DOMParser(); 433 return parser.parseFromString(txt, "text/xml"); 434 } catch (e) { 435 //undefined internal parser error 436 return null; 437 } 438 }, 439 serializeXML:function (xmlNode, escape) { 440 if (!escape) { 441 if (xmlNode.data) return xmlNode.data; //CDATA block has raw data 442 if (xmlNode.textContent) return xmlNode.textContent; //textNode has textContent 443 } 444 return (new XMLSerializer()).serializeToString(xmlNode); 445 }, 446 serializeChilds:function (xmlNode) { 447 var buffer = []; 448 if (!xmlNode.childNodes) return ""; 449 for (var cnt = 0; cnt < xmlNode.childNodes.length; cnt++) { 450 buffer.push(this.serializeXML(xmlNode.childNodes[cnt])); 451 } 452 return buffer.join(""); 453 }, 454 isXMLParseError:function (xmlContent) { 455 //no xml content 456 if (xmlContent == null) return true; 457 var findParseError = function (node) { 458 if (!node || !node.childNodes) return false; 459 for (var cnt = 0; cnt < node.childNodes.length; cnt++) { 460 var childNode = node.childNodes[cnt]; 461 if (childNode.tagName && childNode.tagName == "parsererror") return true; 462 } 463 return false; 464 }; 465 return !xmlContent || 466 (this.exists(xmlContent, "parseError.errorCode") && xmlContent.parseError.errorCode != 0) || 467 findParseError(xmlContent); 468 }, 469 /** 470 * fetches the error message from the xml content 471 * in a browser independent way 472 * 473 * @param xmlContent 474 * @return a map with the following structure {errorMessage: the error Message, sourceText: the text with the error} 475 */ 476 fetchXMLErrorMessage:function (text, xmlContent) { 477 var _t = this; 478 var findParseError = function (node) { 479 if (!node || !node.childNodes) return false; 480 for (var cnt = 0; cnt < node.childNodes.length; cnt++) { 481 var childNode = node.childNodes[cnt]; 482 if (childNode.tagName && childNode.tagName == "parsererror") { 483 var errorMessage = _t.serializeXML(childNode.childNodes[0]); 484 //we now have to determine the row and column position 485 var lastLine = errorMessage.split("\n"); 486 lastLine = lastLine[lastLine.length-1]; 487 var positions = lastLine.match(/[^0-9]*([0-9]+)[^0-9]*([0-9]+)[^0-9]*/); 488 489 var ret = { 490 errorMessage: errorMessage, 491 sourceText: _t.serializeXML(childNode.childNodes[1].childNodes[0]) 492 } 493 if(positions) { 494 ret.line = Math.max(0, parseInt(positions[1])-1); 495 ret.linePos = Math.max(0, parseInt(positions[2])-1); 496 } 497 return ret; 498 } 499 } 500 return null; 501 }; 502 var ret = null; 503 if (!xmlContent) { 504 //chrome does not deliver any further data 505 ret = (this.trim(text || "").length > 0)? {errorMessage:"Illegal response",sourceText:""} : {errorMessage:"Empty Response",sourceText:""}; 506 } else if (this.exists(xmlContent, "parseError.errorCode") && xmlContent.parseError.errorCode != 0) { 507 ret = { 508 errorMessage:xmlContent.parseError.reason, 509 line:Math.max(0, parseInt(xmlContent.parseError.line)-1), 510 linePos:Math.max(0,parseInt(xmlContent.parseError.linepos) -1), 511 sourceText:xmlContent.parseError.srcText 512 }; 513 } else { 514 ret = findParseError(xmlContent); 515 } 516 //we have a line number we now can format the source accordingly 517 if(ret && 'undefined' != typeof ret.line) { 518 var source = ret.sourceText ||""; 519 source = source.split("\n"); 520 if(source.length-1 < ret.line) return ret; 521 source = source[ret.line]; 522 var secondLine = []; 523 var lineLen = (ret.linePos - 2); 524 for(var cnt = 0; cnt < lineLen; cnt++) { 525 secondLine.push(" "); 526 } 527 secondLine.push("^^"); 528 ret.sourceText = source; 529 ret.visualError = secondLine; 530 } 531 return ret; 532 }, 533 534 /** 535 * creates a neutral form data wrapper over an existing form Data element 536 * the wrapper delegates following methods, append 537 * and adds makeFinal as finalizing method which returns the final 538 * send representation of the element 539 * 540 * @param formData an array 541 */ 542 createFormDataDecorator:function (formData) { 543 //we simulate the dom level 2 form element here 544 var _newCls = null; 545 var bufInstance = null; 546 if (!this.FormDataDecoratorArray) { 547 this.FormDataDecoratorArray = function (theFormData) { 548 this._valBuf = theFormData; 549 this._idx = {}; 550 }; 551 _newCls = this.FormDataDecoratorArray; 552 _newCls.prototype.append = function (key, val) { 553 this._valBuf.push([encodeURIComponent(key), encodeURIComponent(val)].join("=")); 554 this._idx[key] = true; 555 }; 556 _newCls.prototype.hasKey = function (key) { 557 return !!this._idx[key]; 558 }; 559 _newCls.prototype.makeFinal = function () { 560 return this._valBuf.join("&"); 561 }; 562 } 563 if (!this.FormDataDecoratorString) { 564 this.FormDataDecoratorString = function (theFormData) { 565 this._preprocessedData = theFormData; 566 this._valBuf = []; 567 this._idx = {}; 568 }; 569 _newCls = this.FormDataDecoratorString; 570 _newCls.prototype.append = function (key, val) { 571 this._valBuf.push([encodeURIComponent(key), encodeURIComponent(val)].join("=")); 572 this._idx[key] = true; 573 }; 574 //for now we check only for keys which are added subsequently otherwise we do not perform any checks 575 _newCls.prototype.hasKey = function (key) { 576 return !!this._idx[key]; 577 }; 578 _newCls.prototype.makeFinal = function () { 579 if (this._preprocessedData != "") { 580 return this._preprocessedData + "&" + this._valBuf.join("&") 581 } else { 582 return this._valBuf.join("&"); 583 } 584 }; 585 } 586 if (!this.FormDataDecoratorOther) { 587 this.FormDataDecoratorOther = function (theFormData) { 588 this._valBuf = theFormData; 589 this._idx = {}; 590 }; 591 _newCls = this.FormDataDecoratorOther; 592 _newCls.prototype.append = function (key, val) { 593 this._valBuf.append(key, val); 594 this._idx[key] = true; 595 }; 596 _newCls.prototype.hasKey = function (key) { 597 return !!this._idx[key]; 598 }; 599 _newCls.prototype.makeFinal = function () { 600 return this._valBuf; 601 }; 602 } 603 if (formData instanceof Array) { 604 bufInstance = new this.FormDataDecoratorArray(formData); 605 } else if (this.isString(formData)) { 606 bufInstance = new this.FormDataDecoratorString(formData); 607 } else { 608 bufInstance = new this.FormDataDecoratorOther(formData); 609 } 610 return bufInstance; 611 }, 612 /** 613 * define a property mechanism which is browser neutral 614 * we cannot use the existing setter and getter mechanisms 615 * for now because old browsers do not support them 616 * in the long run we probably can switch over 617 * or make a code split between legacy and new 618 * 619 * 620 * @param obj 621 * @param name 622 * @param value 623 */ 624 attr:function (obj, name, value) { 625 var findAccessor = function (theObj, theName) { 626 return (theObj["_" + theName]) ? "_" + theName : ( (theObj[theName]) ? theName : null) 627 }; 628 var applyAttr = function (theObj, theName, value, isFunc) { 629 if (value) { 630 if (isFunc) { 631 theObj[theName](value); 632 } else { 633 theObj[theName] = value; 634 } 635 return null; 636 } 637 return (isFunc) ? theObj[theName]() : theObj[theName]; 638 }; 639 try { 640 var finalAttr = findAccessor(obj, name); 641 //simple attibute no setter and getter overrides 642 if (finalAttr) { 643 return applyAttr(obj, finalAttr, value); 644 } 645 //lets check for setter and getter overrides 646 var found = false; 647 var prefix = (value) ? "set" : "get"; 648 finalAttr = [prefix, name.substr(0, 1).toUpperCase(), name.substr(1)].join(""); 649 finalAttr = findAccessor(obj, finalAttr); 650 if (finalAttr) { 651 return applyAttr(obj, finalAttr, value, true); 652 } 653 654 throw this.makeException(new Error(), null, null, this._nameSpace, "contains", "property " + name + " not found"); 655 } finally { 656 findAccessor = null; 657 applyAttr = null; 658 } 659 }, 660 661 /** 662 * creates an exeption with additional internal parameters 663 * for extra information 664 * 665 * @param {String} title the exception title 666 * @param {String} name the exception name 667 * @param {String} callerCls the caller class 668 * @param {String} callFunc the caller function 669 * @param {String} message the message for the exception 670 */ 671 makeException:function (error, title, name, callerCls, callFunc, message) { 672 //var error = new Error(message || ""); 673 error.name = name || "clientError"; 674 error.title = title || ""; 675 error.message = message || ""; 676 error._mfInternal = {}; 677 error._mfInternal.name = name || "clientError"; 678 error._mfInternal.title = title || "clientError"; 679 error._mfInternal.caller = callerCls || this._nameSpace; 680 error._mfInternal.callFunc = callFunc || ("" + arguments.caller.toString()); 681 return error; 682 } 683 }); 684