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