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 /** 18 * @class 19 * @name Impl 20 * @memberOf myfaces._impl.core 21 * @description Implementation singleton which implements all interface method 22 * defined by our jsf.js API 23 * */ 24 _MF_SINGLTN(_PFX_CORE + "Impl", _MF_OBJECT, /** @lends myfaces._impl.core.Impl.prototype */ { 25 26 //third option myfaces._impl.xhrCoreAjax which will be the new core impl for now 27 _transport:myfaces._impl.core._Runtime.getGlobalConfig("transport", myfaces._impl.xhrCore._Transports), 28 29 /** 30 * external event listener queue! 31 */ 32 _evtListeners:new (myfaces._impl.core._Runtime.getGlobalConfig("eventListenerQueue", myfaces._impl._util._ListenerQueue))(), 33 34 /** 35 * external error listener queue! 36 */ 37 _errListeners:new (myfaces._impl.core._Runtime.getGlobalConfig("errorListenerQueue", myfaces._impl._util._ListenerQueue))(), 38 39 /*CONSTANTS*/ 40 41 /*internal identifiers for options*/ 42 IDENT_ALL:"@all", 43 IDENT_NONE:"@none", 44 IDENT_THIS:"@this", 45 IDENT_FORM:"@form", 46 47 /* 48 * [STATIC] constants 49 */ 50 51 P_PARTIAL_SOURCE:"javax.faces.source", 52 P_VIEWSTATE:"javax.faces.ViewState", 53 P_CLIENTWINDOW:"javax.faces.ClientWindow", 54 P_AJAX:"javax.faces.partial.ajax", 55 P_EXECUTE:"javax.faces.partial.execute", 56 P_RENDER:"javax.faces.partial.render", 57 P_EVT:"javax.faces.partial.event", 58 P_BEHAVIOR_EVENT:"javax.faces.behavior.event", 59 P_WINDOW_ID:"javax.faces.ClientWindow", 60 P_RESET_VALUES:"javax.faces.partial.resetValues", 61 62 //faces std values 63 STD_VALUES: [this.P_PARTIAL_SOURCE, this.P_VIEWSTATE, this.P_CLIENTWINDOW, this.P_AJAX, 64 this.P_EXECUTE, this.P_RENDER, this.P_EVT, this.P_BEHAVIOR_EVENT, this.P_WINDOW_ID, this.P_RESET_VALUES], 65 66 /* message types */ 67 ERROR:"error", 68 EVENT:"event", 69 70 /* event emitting stages */ 71 BEGIN:"begin", 72 COMPLETE:"complete", 73 SUCCESS:"success", 74 75 /*ajax errors spec 14.4.2*/ 76 HTTPERROR:"httpError", 77 EMPTY_RESPONSE:"emptyResponse", 78 MALFORMEDXML:"malformedXML", 79 SERVER_ERROR:"serverError", 80 CLIENT_ERROR:"clientError", 81 TIMEOUT_EVENT:"timeout", 82 83 /*error reporting threshold*/ 84 _threshold:"ERROR", 85 86 /*blockfilter for the passthrough filtering, the attributes given here 87 * will not be transmitted from the options into the passthrough*/ 88 _BLOCKFILTER:{onerror:1, onevent:1, render:1, execute:1, myfaces:1, delay:1, resetValues:1, params: 1}, 89 90 /** 91 * collect and encode data for a given form element (must be of type form) 92 * find the javax.faces.ViewState element and encode its value as well! 93 * return a concatenated string of the encoded values! 94 * 95 * @throws Error in case of the given element not being of type form! 96 * https://issues.apache.org/jira/browse/MYFACES-2110 97 */ 98 getViewState:function (form) { 99 /** 100 * typecheck assert!, we opt for strong typing here 101 * because it makes it easier to detect bugs 102 */ 103 if (form) { 104 form = this._Lang.byId(form); 105 } 106 107 if (!form 108 || !form.nodeName 109 || form.nodeName.toLowerCase() != "form") { 110 throw new Error(this._Lang.getMessage("ERR_VIEWSTATE")); 111 } 112 113 var ajaxUtils = myfaces._impl.xhrCore._AjaxUtils; 114 115 var ret = this._Lang.createFormDataDecorator([]); 116 ajaxUtils.encodeSubmittableFields(ret, form, null); 117 118 return ret.makeFinal(); 119 }, 120 121 /** 122 * this function has to send the ajax requests 123 * 124 * following request conditions must be met: 125 * <ul> 126 * <li> the request must be sent asynchronously! </li> 127 * <li> the request must be a POST!!! request </li> 128 * <li> the request url must be the form action attribute </li> 129 * <li> all requests must be queued with a client side request queue to ensure the request ordering!</li> 130 * </ul> 131 * 132 * @param {String|Node} elem any dom element no matter being it html or jsf, from which the event is emitted 133 * @param {|Event|} event any javascript event supported by that object 134 * @param {|Object|} options map of options being pushed into the ajax cycle 135 * 136 * 137 * a) transformArguments out of the function 138 * b) passThrough handling with a map copy with a filter map block map 139 */ 140 request:function (elem, event, options) { 141 if (this._delayTimeout) { 142 clearTimeout(this._delayTimeout); 143 delete this._delayTimeout; 144 } 145 /*namespace remap for our local function context we mix the entire function namespace into 146 *a local function variable so that we do not have to write the entire namespace 147 *all the time 148 **/ 149 var _Lang = this._Lang, 150 _Dom = this._Dom, 151 _Utils = myfaces._impl.xhrCore._AjaxUtils; 152 /*assert if the onerror is set and once if it is set it must be of type function*/ 153 _Lang.assertType(options.onerror, "function"); 154 /*assert if the onevent is set and once if it is set it must be of type function*/ 155 _Lang.assertType(options.onevent, "function"); 156 157 //options not set we define a default one with nothing 158 options = options || {}; 159 160 /** 161 * we cross - reference statically hence the mapping here 162 * the entire mapping between the functions is stateless 163 */ 164 //null definitely means no event passed down so we skip the ie specific checks 165 if ('undefined' == typeof event) { 166 event = window.event || null; 167 } 168 169 //improve the error messages if an empty elem is passed 170 if (!elem) { 171 throw _Lang.makeException(new Error(), "ArgNotSet", null, this._nameSpace, "request", _Lang.getMessage("ERR_MUST_BE_PROVIDED1", "{0}: source must be provided", "jsf.ajax.request", "source element id")); 172 } 173 var oldElem = elem; 174 elem = _Dom.byIdOrName(elem); 175 if (!elem) { 176 throw _Lang.makeException(new Error(), "Notfound", null, this._nameSpace, "request", _Lang.getMessage("ERR_PPR_UNKNOWNCID", "{0}: Node with id {1} could not be found from source", this._nameSpace + ".request", oldElem)); 177 } 178 179 var elementId = _Dom.nodeIdOrName(elem); 180 181 /* 182 * We make a copy of our options because 183 * we should not touch the incoming params! 184 * this copy is also the pass through parameters 185 * which are sent down our request 186 */ 187 // this is legacy behavior which is faulty, will be removed if we decide to do it 188 // that way 189 // TODO not sure whether we add the naming container prefix to the user params 190 var passThrgh = _Lang.mixMaps({}, options, true, this._BLOCKFILTER); 191 // jsdoc spec everything under params must be passed through 192 if(options.params) { 193 passThrgh = _Lang.mixMaps(passThrgh, options.params, true, {}); 194 } 195 196 if (event) { 197 passThrgh[this.P_EVT] = event.type; 198 } 199 200 201 202 /** 203 * ajax pass through context with the source 204 * onevent and onerror 205 */ 206 var context = { 207 source:elem, 208 onevent:options.onevent, 209 onerror:options.onerror, 210 viewId: "", 211 //TODO move the myfaces part into the _mfInternal part 212 myfaces:options.myfaces, 213 _mfInternal:{} 214 }; 215 //additional meta information to speed things up, note internal non jsf 216 //pass through options are stored under _mfInternal in the context 217 var mfInternal = context._mfInternal; 218 219 /** 220 * fetch the parent form 221 * 222 * note we also add an override possibility here 223 * so that people can use dummy forms and work 224 * with detached objects 225 */ 226 var form = (options.myfaces && options.myfaces.form) ? 227 _Lang.byId(options.myfaces.form) : 228 this._getForm(elem, event); 229 230 context.viewId = this.getViewId(form); 231 232 /** 233 * we also now assign the container data to deal with it later 234 */ 235 _Utils._assignNamingContainerData(mfInternal, form, jsf.separatorchar); 236 237 238 /** 239 * JSF2.2 client window must be part of the issuing form so it is encoded 240 * automatically in the request 241 */ 242 //we set the client window before encoding by a call to jsf.getClientWindow 243 var clientWindow = jsf.getClientWindow(form); 244 //in case someone decorates the getClientWindow we reset the value from 245 //what we are getting 246 if ('undefined' != typeof clientWindow && null != clientWindow) { 247 var formElem = _Dom.getNamedElementFromForm(form, _Utils._$ncRemap(mfInternal, this.P_CLIENTWINDOW)); 248 if (formElem) { 249 //we store the value for later processing during the ajax phase 250 //job so that we do not get double values 251 context._mfInternal._clientWindow = jsf.getClientWindow(form); 252 } else { 253 passThrgh[this.P_CLIENTWINDOW] = jsf.getClientWindow(form); 254 } 255 } /* spec proposal 256 else { 257 var formElem = _Dom.getNamedElementFromForm(form, this.P_CLIENTWINDOW); 258 if (formElem) { 259 context._mfInternal._clientWindow = "undefined"; 260 } else { 261 passThrgh[this.P_CLIENTWINDOW] = "undefined"; 262 } 263 } 264 */ 265 266 /** 267 * binding contract the javax.faces.source must be set 268 */ 269 passThrgh[this.P_PARTIAL_SOURCE] = elementId; 270 271 /** 272 * javax.faces.partial.ajax must be set to true 273 */ 274 passThrgh[this.P_AJAX] = true; 275 276 /** 277 * if resetValues is set to true 278 * then we have to set javax.faces.resetValues as well 279 * as pass through parameter 280 * the value has to be explicitly true, according to 281 * the specs jsdoc 282 */ 283 if(options.resetValues === true) { 284 passThrgh[this.P_RESET_VALUES] = true; 285 } 286 287 if (options.execute) { 288 /*the options must be a blank delimited list of strings*/ 289 /*compliance with Mojarra which automatically adds @this to an execute 290 * the spec rev 2.0a however states, if none is issued nothing at all should be sent down 291 */ 292 options.execute = (options.execute.indexOf("@this") == -1) ? options.execute : options.execute; 293 294 this._transformList(passThrgh, this.P_EXECUTE, options.execute, form, elementId, context.viewId); 295 } else { 296 passThrgh[this.P_EXECUTE] = elementId; 297 } 298 299 if (options.render) { 300 this._transformList(passThrgh, this.P_RENDER, options.render, form, elementId, context.viewId); 301 } 302 303 /** 304 * multiple transports upcoming jsf 2.x feature currently allowed 305 * default (no value) xhrQueuedPost 306 * 307 * xhrQueuedPost 308 * xhrPost 309 * xhrGet 310 * xhrQueuedGet 311 * iframePost 312 * iframeQueuedPost 313 * 314 */ 315 var transportType = this._getTransportType(context, passThrgh, form); 316 317 mfInternal["_mfSourceFormId"] = form.id; 318 mfInternal["_mfSourceControlId"] = elementId; 319 mfInternal["_mfTransportType"] = transportType; 320 321 //mojarra compatibility, mojarra is sending the form id as well 322 //this is not documented behavior but can be determined by running 323 //mojarra under blackbox conditions 324 //i assume it does the same as our formId_submit=1 so leaving it out 325 //won´t hurt but for the sake of compatibility we are going to add it 326 passThrgh[form.id] = form.id; 327 328 /* jsf2.2 only: options.delay || */ 329 330 // TCK 790 we now have to remap all passthroughs in case of a naming container 331 // thing is the naming container is always prefixed on inputs, and our own 332 // passthroughs are not mapped for now (if we have to do that we we have to add a similar mapping code) 333 var passthroughKeys = Object.keys(passThrgh); 334 for(var key in passthroughKeys) { 335 if(!Object.hasOwnProperty(key) || this.STD_VALUES.indexOf(key) == -1) { 336 continue; 337 } 338 passThrgh[_Utils._$ncRemap(mfInternal, key)] = passThrgh[key]; 339 delete passThrgh[key]; 340 } 341 342 /* faces2.2 only: options.delay || */ 343 var delayTimeout = options.delay || this._RT.getLocalOrGlobalConfig(context, "delay", false); 344 345 if (!!delayTimeout) { 346 if(delayTimeout.toLowerCase && delayTimeout.toLowerCase() === "none"){ 347 delayTimeout = 0; 348 } 349 if(!(delayTimeout >= 0)) { 350 // abbreviation which covers all cases of non positive values, 351 // including NaN and non-numeric strings, no type equality is deliberate here, 352 throw new Error("Invalid delay value: " + delayTimeout); 353 } 354 if (this._delayTimeout) { 355 clearTimeout(this._delayTimeout); 356 } 357 this._delayTimeout = setTimeout(_Lang.hitch(this, function () { 358 this._transport[transportType](elem, form, context, passThrgh); 359 this._delayTimeout = null; 360 }), parseInt(delayTimeout)); 361 } else { 362 this._transport[transportType](elem, form, context, passThrgh); 363 } 364 }, 365 366 /** 367 * fetches the form in an unprecise manner depending 368 * on an element or event target 369 * 370 * @param elem 371 * @param event 372 */ 373 _getForm:function (elem, event) { 374 var _Dom = this._Dom; 375 var _Lang = this._Lang; 376 var form = _Dom.fuzzyFormDetection(elem); 377 378 if (!form && event) { 379 //in case of no form is given we retry over the issuing event 380 form = _Dom.fuzzyFormDetection(_Lang.getEventTarget(event)); 381 if (!form) { 382 throw _Lang.makeException(new Error(), null, null, this._nameSpace, "_getForm", _Lang.getMessage("ERR_FORM")); 383 } 384 } else if (!form) { 385 throw _Lang.makeException(new Error(), null, null, this._nameSpace, "_getForm", _Lang.getMessage("ERR_FORM")); 386 387 } 388 return form; 389 }, 390 391 /** 392 * determines the transport type to be called 393 * for the ajax call 394 * 395 * @param context the context 396 * @param passThrgh pass through values 397 * @param form the form which issues the request 398 */ 399 _getTransportType:function (context, passThrgh, form) { 400 /** 401 * if execute or render exist 402 * we have to pass them down as a blank delimited string representation 403 * of an array of ids! 404 */ 405 //for now we turn off the transport auto selection, to enable 2.0 backwards compatibility 406 //on protocol level, the file upload only can be turned on if the auto selection is set to true 407 var getConfig = this._RT.getLocalOrGlobalConfig, 408 _Lang = this._Lang, 409 _Dom = this._Dom; 410 411 var transportAutoSelection = getConfig(context, "transportAutoSelection", true); 412 /*var isMultipart = (transportAutoSelection && _Dom.getAttribute(form, "enctype") == "multipart/form-data") ? 413 _Dom.isMultipartCandidate((!getConfig(context, "pps",false))? form : passThrgh[this.P_EXECUTE]) : 414 false; 415 **/ 416 if (!transportAutoSelection) { 417 return getConfig(context, "transportType", "xhrQueuedPost"); 418 } 419 var multiPartCandidate = _Dom.isMultipartCandidate((!getConfig(context, "pps", false)) ? 420 form : passThrgh[this.P_EXECUTE]); 421 var multipartForm = (_Dom.getAttribute(form, "enctype") || "").toLowerCase() == "multipart/form-data"; 422 //spec section jsdoc, if we have a multipart candidate in our execute (aka fileupload) 423 //and the form is not multipart then we have to raise an error 424 if (multiPartCandidate && !multipartForm) { 425 throw _Lang.makeException(new Error(), null, null, this._nameSpace, "_getTransportType", _Lang.getMessage("ERR_NO_MULTIPART_FORM", "No Multipart form", form.id)); 426 } 427 var isMultipart = multiPartCandidate && multipartForm; 428 /** 429 * multiple transports upcoming jsf 2.2 feature currently allowed 430 * default (no value) xhrQueuedPost 431 * 432 * xhrQueuedPost 433 * xhrPost 434 * xhrGet 435 * xhrQueuedGet 436 * iframePost 437 * iframeQueuedPost 438 * 439 */ 440 var transportType = (!isMultipart) ? 441 getConfig(context, "transportType", "xhrQueuedPost") : 442 getConfig(context, "transportType", "multipartQueuedPost"); 443 if (!this._transport[transportType]) { 444 //throw new Error("Transport type " + transportType + " does not exist"); 445 throw new Error(_Lang.getMessage("ERR_TRANSPORT", null, transportType)); 446 } 447 return transportType; 448 449 }, 450 451 /** 452 * transforms the list to the expected one 453 * with the proper none all form and this handling 454 * (note we also could use a simple string replace but then 455 * we would have had double entries under some circumstances) 456 * 457 * @param passThrgh 458 * @param target 459 * @param srcStr 460 * @param form 461 * @param elementId 462 * @param namingContainerId the naming container namingContainerId 463 */ 464 _transformList:function (passThrgh, target, srcStr, form, elementId, namingContainerId) { 465 var _Lang = this._Lang; 466 //this is probably the fastest transformation method 467 //it uses an array and an index to position all elements correctly 468 //the offset variable is there to prevent 0 which results in a javascript 469 //false 470 srcStr = this._Lang.trim(srcStr); 471 var offset = 1, 472 vals = (srcStr) ? srcStr.split(/\s+/) : [], 473 idIdx = (vals.length) ? _Lang.arrToMap(vals, offset) : {}, 474 475 //helpers to improve speed and compression 476 none = idIdx[this.IDENT_NONE], 477 all = idIdx[this.IDENT_ALL], 478 theThis = idIdx[this.IDENT_THIS], 479 theForm = idIdx[this.IDENT_FORM]; 480 481 if (none) { 482 //in case of none nothing is returned 483 if ('undefined' != typeof passThrgh.target) { 484 delete passThrgh.target; 485 } 486 return passThrgh; 487 } 488 if (all) { 489 //in case of all only one value is returned 490 passThrgh[target] = this.IDENT_ALL; 491 return passThrgh; 492 } 493 494 if (theForm) { 495 //the form is replaced with the proper id but the other 496 //values are not touched 497 vals[theForm - offset] = form.id; 498 } 499 if (theThis && !idIdx[elementId]) { 500 //in case of this, the element id is set 501 vals[theThis - offset] = elementId; 502 } 503 504 //the final list must be blank separated 505 passThrgh[target] = this._remapNamingContainer(elementId, form, namingContainerId,vals).join(" "); 506 return passThrgh; 507 }, 508 509 /** 510 * in namespaced situations root naming containers must be resolved 511 * ":" absolute searches must be mapped accordingly, the same 512 * goes for absolut searches containing already the root naming container id 513 * 514 * @param issuingElementId the issuing element id 515 * @param form the hosting form of the issiung element id 516 * @param rootNamingContainerId the root naming container id 517 * @param elements a list of element client ids to be processed 518 * @returns {*} the mapped element client ids, which are resolved correctly to their naming containers 519 * @private 520 */ 521 _remapNamingContainer: function(issuingElementId, form, rootNamingContainerId, elements) { 522 var SEP = jsf.separatorchar; 523 function remapViewId(toTransform) { 524 var EMPTY_STR = ""; 525 var rootNamingContainerPrefix = (rootNamingContainerId.length) ? rootNamingContainerId+SEP : EMPTY_STR; 526 var formClientId = form.id; 527 // nearest parent naming container relative to the form 528 var nearestNamingContainer = formClientId.substring(0, formClientId.lastIndexOf(SEP)); 529 var nearestNamingContainerPrefix = (nearestNamingContainer.length) ? nearestNamingContainer + SEP : EMPTY_STR; 530 // absolut search expression, always starts with SEP or the name of the root naming container 531 var hasLeadingSep = toTransform.indexOf(SEP) === 0; 532 var isAbsolutSearchExpr = hasLeadingSep || (rootNamingContainerId.length 533 && toTransform.indexOf(rootNamingContainerPrefix) == 0); 534 if (isAbsolutSearchExpr) { 535 //we cut off the leading sep if there is one 536 toTransform = hasLeadingSep ? toTransform.substring(1) : toTransform; 537 toTransform = toTransform.indexOf(rootNamingContainerPrefix) == 0 ? toTransform.substring(rootNamingContainerPrefix.length) : toTransform; 538 //now we prepend either the prefix or "" from the cut-off string to get the final result 539 return [rootNamingContainerPrefix, toTransform].join(EMPTY_STR); 540 } else { //relative search according to the javadoc 541 //we cut off the root naming container id from the form 542 if (formClientId.indexOf(rootNamingContainerPrefix) == 0) { 543 formClientId = formClientId.substring(rootNamingContainerPrefix.length); 544 } 545 546 //If prependId = true, the outer form id must be present in the id if same form 547 var hasPrependId = toTransform.indexOf(formClientId) == 0; 548 549 return hasPrependId ? 550 [rootNamingContainerPrefix, toTransform].join(EMPTY_STR) : 551 [nearestNamingContainerPrefix, toTransform].join(EMPTY_STR); 552 } 553 } 554 555 for(var cnt = 0; cnt < elements.length; cnt++) { 556 elements[cnt] = remapViewId(this._Lang.trim(elements[cnt])); 557 } 558 559 return elements; 560 }, 561 562 addOnError:function (/*function*/errorListener) { 563 /*error handling already done in the assert of the queue*/ 564 this._errListeners.enqueue(errorListener); 565 }, 566 567 addOnEvent:function (/*function*/eventListener) { 568 /*error handling already done in the assert of the queue*/ 569 this._evtListeners.enqueue(eventListener); 570 }, 571 572 /** 573 * implementation triggering the error chain 574 * 575 * @param {Object} request the request object which comes from the xhr cycle 576 * @param {Object} context (Map) the context object being pushed over the xhr cycle keeping additional metadata 577 * @param {String} name the error name 578 * @param {String} errorName the server error name in case of a server error 579 * @param {String} errorMessage the server error message in case of a server error 580 * @param {String} caller optional caller reference for extended error messages 581 * @param {String} callFunc optional caller Function reference for extended error messages 582 * 583 * handles the errors, in case of an onError exists within the context the onError is called as local error handler 584 * the registered error handlers in the queue receiv an error message to be dealt with 585 * and if the projectStage is at development an alert box is displayed 586 * 587 * note: we have additional functionality here, via the global config myfaces.config.defaultErrorOutput a function can be provided 588 * which changes the default output behavior from alert to something else 589 * 590 * 591 */ 592 sendError:function sendError(/*Object*/request, /*Object*/ context, /*String*/ name, /*String*/ errorName, /*String*/ errorMessage, caller, callFunc) { 593 var _Lang = myfaces._impl._util._Lang; 594 var UNKNOWN = _Lang.getMessage("UNKNOWN"); 595 596 var eventData = {}; 597 //we keep this in a closure because we might reuse it for our errorMessage 598 var malFormedMessage = function () { 599 return (name && name === myfaces._impl.core.Impl.MALFORMEDXML) ? _Lang.getMessage("ERR_MALFORMEDXML") : ""; 600 }; 601 602 //by setting unknown values to unknown we can handle cases 603 //better where a simulated context is pushed into the system 604 eventData.type = this.ERROR; 605 606 eventData.status = name || UNKNOWN; 607 eventData.errorName = errorName || UNKNOWN; 608 eventData.errorMessage = errorMessage || UNKNOWN; 609 610 try { 611 eventData.source = context.source || UNKNOWN; 612 eventData.responseCode = request.status || UNKNOWN; 613 eventData.responseText = request.responseText || UNKNOWN; 614 eventData.responseXML = request.responseXML || UNKNOWN; 615 } catch (e) { 616 // silently ignore: user can find out by examining the event data 617 } 618 //extended error message only in dev mode 619 if (jsf.getProjectStage() === "Development") { 620 eventData.errorMessage = eventData.errorMessage || ""; 621 eventData.errorMessage = (caller) ? eventData.errorMessage + "\nCalling class: " + caller : eventData.errorMessage; 622 eventData.errorMessage = (callFunc) ? eventData.errorMessage + "\n Calling function: " + callFunc : eventData.errorMessage; 623 } 624 625 /**/ 626 if (context["onerror"]) { 627 context.onerror(eventData); 628 } 629 630 /*now we serve the queue as well*/ 631 this._errListeners.broadcastEvent(eventData); 632 633 if (jsf.getProjectStage() === "Development" && this._errListeners.length() == 0 && !context["onerror"]) { 634 var DIVIDER = "--------------------------------------------------------", 635 defaultErrorOutput = myfaces._impl.core._Runtime.getGlobalConfig("defaultErrorOutput", (console && console.error) ? console.error : alert), 636 finalMessage = [], 637 //we remap the function to achieve a better compressability 638 pushMsg = _Lang.hitch(finalMessage, finalMessage.push); 639 640 (errorMessage) ? pushMsg(_Lang.getMessage("MSG_ERROR_MESSAGE") + " " + errorMessage + "\n") : null; 641 642 pushMsg(DIVIDER); 643 644 (caller) ? pushMsg("Calling class:" + caller) : null; 645 (callFunc) ? pushMsg("Calling function:" + callFunc) : null; 646 (name) ? pushMsg(_Lang.getMessage("MSG_ERROR_NAME") + " " + name) : null; 647 (errorName && name != errorName) ? pushMsg("Server error name: " + errorName) : null; 648 649 pushMsg(malFormedMessage()); 650 pushMsg(DIVIDER); 651 pushMsg(_Lang.getMessage("MSG_DEV_MODE")); 652 defaultErrorOutput(finalMessage.join("\n")); 653 } 654 }, 655 656 /** 657 * sends an event 658 */ 659 sendEvent:function sendEvent(/*Object*/request, /*Object*/ context, /*event name*/ name) { 660 var _Lang = myfaces._impl._util._Lang; 661 var eventData = {}; 662 var UNKNOWN = _Lang.getMessage("UNKNOWN"); 663 664 eventData.type = this.EVENT; 665 666 eventData.status = name; 667 eventData.source = context.source; 668 669 if (name !== this.BEGIN) { 670 671 try { 672 //we bypass a problem with ie here, ie throws an exception if no status is given on the xhr object instead of just passing a value 673 var getValue = function (value, key) { 674 try { 675 return value[key] 676 } catch (e) { 677 return UNKNOWN; 678 } 679 }; 680 681 eventData.responseCode = getValue(request, "status"); 682 eventData.responseText = getValue(request, "responseText"); 683 eventData.responseXML = getValue(request, "responseXML"); 684 685 } catch (e) { 686 var impl = myfaces._impl.core._Runtime.getGlobalConfig("jsfAjaxImpl", myfaces._impl.core.Impl); 687 impl.sendError(request, context, this.CLIENT_ERROR, "ErrorRetrievingResponse", 688 _Lang.getMessage("ERR_CONSTRUCT", e.toString())); 689 690 //client errors are not swallowed 691 throw e; 692 } 693 694 } 695 696 /**/ 697 if (context.onevent) { 698 /*calling null to preserve the original scope*/ 699 context.onevent.call(null, eventData); 700 } 701 702 /*now we serve the queue as well*/ 703 this._evtListeners.broadcastEvent(eventData); 704 }, 705 706 /** 707 * Spec. 13.3.3 708 * Examining the response markup and updating the DOM tree 709 * @param {XMLHttpRequest} request - the ajax request 710 * @param {Object} context - the ajax context 711 */ 712 response:function (request, context) { 713 this._RT.getLocalOrGlobalConfig(context, "responseHandler", myfaces._impl.xhrCore._AjaxResponse).processResponse(request, context); 714 }, 715 716 /** 717 * fetches the separator char from the given script tags 718 * 719 * @return {char} the separator char for the given script tags 720 */ 721 getSeparatorChar:function () { 722 if (this._separator) { 723 return this.separatorchar; 724 } 725 var SEPARATOR_CHAR = "separatorchar", 726 found = false, 727 getConfig = myfaces._impl.core._Runtime.getGlobalConfig, 728 scriptTags = document.getElementsByTagName("script"); 729 for (var i = 0; i < scriptTags.length && !found; i++) { 730 if (scriptTags[i].src.search(/\/javax\.faces\.resource.*\/jsf\.js.*separator/) != -1) { 731 found = true; 732 var result = scriptTags[i].src.match(/separator=([^&;]*)/); 733 this._separator = decodeURIComponent(result[1]); 734 } 735 } 736 this._separator = getConfig(SEPARATOR_CHAR, this._separator || ":"); 737 return this._separator; 738 }, 739 740 /** 741 * @return the project stage also emitted by the server: 742 * it cannot be cached and must be delivered over the server 743 * The value for it comes from the request parameter of the jsf.js script called "stage". 744 */ 745 getProjectStage:function () { 746 //since impl is a singleton we only have to do it once at first access 747 748 if (!this._projectStage) { 749 var PRJ_STAGE = "projectStage", 750 STG_PROD = "Production", 751 752 scriptTags = document.getElementsByTagName("script"), 753 getConfig = myfaces._impl.core._Runtime.getGlobalConfig, 754 projectStage = null, 755 found = false, 756 allowedProjectStages = {STG_PROD:1, "Development":1, "SystemTest":1, "UnitTest":1}; 757 758 /* run through all script tags and try to find the one that includes jsf.js */ 759 for (var i = 0; i < scriptTags.length && !found; i++) { 760 if (scriptTags[i] && scriptTags[i].src && scriptTags[i].src.search(/\/javax\.faces\.resource\/jsf\.js.*ln=javax\.faces/) != -1) { 761 var result = scriptTags[i].src.match(/stage=([^&;]*)/); 762 found = true; 763 if (result) { 764 // we found stage=XXX 765 // return only valid values of ProjectStage 766 projectStage = (allowedProjectStages[result[1]]) ? result[1] : null; 767 768 } 769 else { 770 //we found the script, but there was no stage parameter -- Production 771 //(we also add an override here for testing purposes, the default, however is Production) 772 projectStage = getConfig(PRJ_STAGE, STG_PROD); 773 } 774 } 775 } 776 /* we could not find anything valid --> return the default value */ 777 this._projectStage = getConfig(PRJ_STAGE, projectStage || STG_PROD); 778 } 779 return this._projectStage; 780 }, 781 782 /** 783 * implementation of the external chain function 784 * moved into the impl 785 * 786 * @param {Object} source the source which also becomes 787 * the scope for the calling function (unspecified side behavior) 788 * the spec states here that the source can be any arbitrary code block. 789 * Which means it either is a javascript function directly passed or a code block 790 * which has to be evaluated separately. 791 * 792 * After revisiting the code additional testing against components showed that 793 * the this parameter is only targeted at the component triggering the eval 794 * (event) if a string code block is passed. This is behavior we have to resemble 795 * in our function here as well, I guess. 796 * 797 * @param {Event} event the event object being passed down into the the chain as event origin 798 * the spec is contradicting here, it on one hand defines event, and on the other 799 * it says it is optional, after asking, it meant that event must be passed down 800 * but can be undefined 801 */ 802 chain:function (source, event) { 803 var len = arguments.length; 804 var _Lang = this._Lang; 805 var throwErr = function (msgKey) { 806 throw Error("jsf.util.chain: " + _Lang.getMessage(msgKey)); 807 }; 808 /** 809 * generic error condition checker which raises 810 * an exception if the condition is met 811 * @param assertion 812 * @param message 813 */ 814 var errorCondition = function (assertion, message) { 815 if (assertion === true) throwErr(message); 816 }; 817 var FUNC = 'function'; 818 var ISSTR = _Lang.isString; 819 820 //the spec is contradicting here, it on one hand defines event, and on the other 821 //it says it is optional, I have cleared this up now 822 //the spec meant the param must be passed down, but can be 'undefined' 823 824 errorCondition(len < 2, "ERR_EV_OR_UNKNOWN"); 825 errorCondition(len < 3 && (FUNC == typeof event || ISSTR(event)), "ERR_EVT_PASS"); 826 if (len < 3) { 827 //nothing to be done here, move along 828 return true; 829 } 830 //now we fetch from what is given from the parameter list 831 //we cannot work with splice here in any performant way so we do it the hard way 832 //arguments only are give if not set to undefined even null values! 833 834 //assertions source either null or set as dom element: 835 errorCondition('undefined' == typeof source, "ERR_SOURCE_DEF_NULL"); 836 errorCondition(FUNC == typeof source, "ERR_SOURCE_FUNC"); 837 errorCondition(ISSTR(source), "ERR_SOURCE_NOSTR"); 838 839 //assertion if event is a function or a string we already are in our function elements 840 //since event either is undefined, null or a valid event object 841 errorCondition(FUNC == typeof event || ISSTR(event), "ERR_EV_OR_UNKNOWN"); 842 843 for (var cnt = 2; cnt < len; cnt++) { 844 //we do not change the scope of the incoming functions 845 //but we reuse the argument array capabilities of apply 846 var ret; 847 848 if (FUNC == typeof arguments[cnt]) { 849 ret = arguments[cnt].call(source, event); 850 } else { 851 //either a function or a string can be passed in case of a string we have to wrap it into another function 852 ret = new Function("event", arguments[cnt]).call(source, event); 853 } 854 //now if one function returns false in between we stop the execution of the cycle 855 //here, note we do a strong comparison here to avoid constructs like 'false' or null triggering 856 if (ret === false /*undefined check implicitly done here by using a strong compare*/) { 857 return false; 858 } 859 } 860 return true; 861 }, 862 863 /** 864 * error handler behavior called internally 865 * and only into the impl it takes care of the 866 * internal message transformation to a myfaces internal error 867 * and then uses the standard send error mechanisms 868 * also a double error logging prevention is done as well 869 * 870 * @param request the request currently being processed 871 * @param context the context affected by this error 872 * @param exception the exception being thrown 873 */ 874 stdErrorHandler:function (request, context, exception) { 875 //newer browsers do not allow to hold additional values on native objects like exceptions 876 //we hence capsule it into the request, which is gced automatically 877 //on ie as well, since the stdErrorHandler usually is called between requests 878 //this is a valid approach 879 if (this._threshold == "ERROR") { 880 var mfInternal = exception._mfInternal || {}; 881 882 var finalMsg = []; 883 finalMsg.push(exception.message); 884 this.sendError(request, context, 885 mfInternal.title || this.CLIENT_ERROR, mfInternal.name || exception.name, finalMsg.join("\n"), mfInternal.caller, mfInternal.callFunc); 886 } 887 }, 888 889 /** 890 * @return the client window id of the current window, if one is given 891 */ 892 getClientWindow:function (node) { 893 var fetchWindowIdFromForms = this._Lang.hitch(this, function (forms) { 894 var result_idx = {}; 895 var result; 896 var foundCnt = 0; 897 for (var cnt = forms.length - 1; cnt >= 0; cnt--) { 898 899 var currentForm = forms[cnt]; 900 var winIdElement = this._Dom.getNamedElementFromForm(currentForm, this.P_WINDOW_ID); 901 var windowId = (winIdElement) ? winIdElement.value : null; 902 903 if (windowId) { 904 if (foundCnt > 0 && "undefined" == typeof result_idx[windowId]) throw Error("Multiple different windowIds found in document"); 905 result = windowId; 906 result_idx[windowId] = true; 907 foundCnt++; 908 } 909 } 910 return result; 911 }); 912 913 var fetchWindowIdFromURL = function () { 914 var href = window.location.href, windowId = "jfwid"; 915 var regex = new RegExp("[\\?&]" + windowId + "=([^\\;]*)"); 916 var results = regex.exec(href); 917 //initial trial over the url and a regexp 918 if (results != null) return results[1]; 919 return null; 920 }; 921 922 //byId ($) 923 var finalNode = (node) ? this._Dom.byId(node) : document.body; 924 925 var forms = this._Dom.findByTagName(finalNode, "form"); 926 var result = fetchWindowIdFromForms(forms); 927 return (null != result) ? result : fetchWindowIdFromURL(); 928 }, 929 930 /** 931 * returns the view id from an incoming form 932 * crossport from new codebase 933 * @param form 934 */ 935 getViewId: function (form) { 936 var _t = this; 937 var foundViewStates = this._Dom.findAll(form, function(node) { 938 return node.tagName === "INPUT" && node.type === "hidden" && (node.name || "").indexOf(_t.P_VIEWSTATE) !== -1 939 }, true); 940 if(!foundViewStates.length) { 941 return ""; 942 } 943 var viewId = foundViewStates[0].id.split(jsf.separatorchar, 2)[0]; 944 var viewStateViewId = viewId.indexOf(this.P_VIEWSTATE) === -1 ? viewId : ""; 945 // myfaces specific, we in non portlet environments prepend the viewId 946 // even without being in a naming container, the other components ignore that 947 return form.id.indexOf(viewStateViewId) === 0 ? viewStateViewId : ""; 948 } 949 }); 950 951 952