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 _AjaxResponse 20 * @memberOf myfaces._impl.xhrCore 21 * @extends myfaces._impl.core.Object 22 * @description 23 * This singleton is responsible for handling the standardized xml ajax response 24 * Note: since the semantic processing can be handled about 90% in a functional 25 * style we make this class stateless. Every state information is stored 26 * temporarily in the context. 27 * 28 * The singleton approach also improves performance 29 * due to less object gc compared to the old instance approach. 30 * 31 */ 32 _MF_SINGLTN(_PFX_XHR + "_AjaxResponse", _MF_OBJECT, /** @lends myfaces._impl.xhrCore._AjaxResponse.prototype */ { 33 34 /*partial response types*/ 35 RESP_PARTIAL: "partial-response", 36 RESP_TYPE_ERROR: "error", 37 RESP_TYPE_REDIRECT: "redirect", 38 RESP_TYPE_CHANGES: "changes", 39 40 /*partial commands*/ 41 CMD_CHANGES: "changes", 42 CMD_UPDATE: "update", 43 CMD_DELETE: "delete", 44 CMD_INSERT: "insert", 45 CMD_EVAL: "eval", 46 CMD_ERROR: "error", 47 CMD_ATTRIBUTES: "attributes", 48 CMD_EXTENSION: "extension", 49 CMD_REDIRECT: "redirect", 50 51 /*other constants*/ 52 P_VIEWSTATE: "jakarta.faces.ViewState", 53 P_CLIENTWINDOW: "jakarta.faces.ClientWindow", 54 P_VIEWROOT: "jakarta.faces.ViewRoot", 55 P_VIEWHEAD: "jakarta.faces.ViewHead", 56 P_VIEWBODY: "jakarta.faces.ViewBody", 57 P_RESOURCE: "jakarta.faces.Resource", 58 59 /** 60 * uses response to start Html element replacement 61 * 62 * @param {Object} request (xhrRequest) - xhr request object 63 * @param {Object} context (Map) - AJAX context 64 * 65 * A special handling has to be added to the update cycle 66 * according to the JSDoc specs if the CDATA block contains html tags the outer rim must be stripped 67 * if the CDATA block contains a head section the document head must be replaced 68 * and if the CDATA block contains a body section the document body must be replaced! 69 * 70 */ 71 processResponse: function (request, context) { 72 //mfinternal handling, note, the mfinternal is only optional 73 //according to the spec 74 context._mfInternal = context._mfInternal || {}; 75 var mfInternal = context._mfInternal; 76 77 //the temporary data is hosted here 78 mfInternal._updateElems = []; 79 mfInternal._updateForms = []; 80 mfInternal.appliedViewState = null; 81 mfInternal.appliedClientWindow = null; 82 mfInternal.namingModeId = null; 83 84 85 try { 86 var _Impl = this.attr("impl"), _Lang = this._Lang; 87 // TODO: 88 // Solution from 89 // http://www.codingforums.com/archive/index.php/t-47018.html 90 // to solve IE error 1072896658 when a Java server sends iso88591 91 // istead of ISO-8859-1 92 93 if (!request || !_Lang.exists(request, "responseXML")) { 94 throw this.makeException(new Error(), _Impl.EMPTY_RESPONSE, _Impl.EMPTY_RESPONSE, this._nameSpace, "processResponse", ""); 95 } 96 //check for a parseError under certain browsers 97 98 var xmlContent = request.responseXML; 99 //ie6+ keeps the parsing response under xmlContent.parserError 100 //while the rest of the world keeps it as element under the first node 101 var xmlErr = _Lang.fetchXMLErrorMessage(request.responseText || request.response, xmlContent) 102 if (xmlErr) { 103 throw this._raiseError(new Error(), xmlErr.errorMessage + "\n" + xmlErr.sourceText + "\n" + xmlErr.visualError + "\n", "processResponse"); 104 } 105 var partials = xmlContent.childNodes[0]; 106 if ('undefined' == typeof partials || partials == null) { 107 throw this._raiseError(new Error(), "No child nodes for response", "processResponse"); 108 109 } else { 110 if (partials.tagName != this.RESP_PARTIAL) { 111 // IE 8 sees XML Header as first sibling ... 112 partials = partials.nextSibling; 113 if (!partials || partials.tagName != this.RESP_PARTIAL) { 114 throw this._raiseError(new Error(), "Partial response not set", "processResponse"); 115 } 116 } 117 } 118 119 120 /** 121 * jsf 2.3 naming mode partial response, 122 * we either viewstate all forms (non id mode) 123 * or the forms under the viewroot defined by id 124 * 125 * @type {string} ... the naming mode id is set or an empty string 126 * definitely not a null value to avoid type confusions later on 127 */ 128 mfInternal.namingModeId = (partials.id || ""); 129 130 131 var childNodesLength = partials.childNodes.length; 132 133 for (var loop = 0; loop < childNodesLength; loop++) { 134 var childNode = partials.childNodes[loop]; 135 var tagName = childNode.tagName; 136 /** 137 * <eval> 138 * <![CDATA[javascript]]> 139 * </eval> 140 */ 141 142 //this ought to be enough for eval 143 //however the run scripts still makes sense 144 //in the update and insert area for components 145 //which do not use the response writer properly 146 //we might add this one as custom option in update and 147 //insert! 148 if (tagName == this.CMD_ERROR) { 149 this.processError(request, context, childNode); 150 } else if (tagName == this.CMD_REDIRECT) { 151 this.processRedirect(request, context, childNode); 152 } else if (tagName == this.CMD_CHANGES) { 153 this.processChanges(request, context, childNode); 154 } 155 } 156 157 //fixup missing viewStates due to spec deficiencies 158 if (mfInternal.appliedViewState) { 159 this.fixViewStates(context); 160 } 161 if (mfInternal.appliedClientWindow) { 162 this.fixClientWindows(context); 163 } 164 165 //spec jsdoc, the success event must be sent from response 166 _Impl.sendEvent(request, context, _Impl["SUCCESS"]); 167 } catch (e) { 168 169 if (window.console && window.console.error) { 170 //any error should be logged 171 console.error(e); 172 } 173 throw e; 174 } finally { 175 delete mfInternal._updateElems; 176 delete mfInternal._updateForms; 177 delete mfInternal.appliedViewState; 178 delete mfInternal.appliedClientWindow; 179 delete mfInternal.namingModeId; 180 } 181 }, 182 183 /** 184 * fixes the viewstates in the current page 185 * 186 * @param context 187 */ 188 fixViewStates: function (context) { 189 var _Lang = this._Lang; 190 var mfInternal = context._mfInternal; 191 192 if (null == mfInternal.appliedViewState) { 193 return; 194 } 195 196 /** 197 * JSF 2.3 we set all the viewstates under a given declared viewRoot or all forms 198 * if none is given 199 */ 200 this._updateJSFClientArtifacts(context, mfInternal.appliedViewState, this.P_VIEWSTATE); 201 }, 202 203 204 fixClientWindows: function (context, theForm) { 205 var _Lang = this._Lang; 206 var mfInternal = context._mfInternal; 207 208 if (null == mfInternal.appliedClientWindow) { 209 return; 210 } 211 212 /** 213 * JSF 2.3 we set all the viewstates under a given declared viewRoot or all forms 214 * if none is given 215 */ 216 217 this._updateJSFClientArtifacts(context, mfInternal.appliedClientWindow, this.P_CLIENTWINDOW); 218 219 }, 220 221 222 /** 223 * sets the a jsf artifact element with a given identifier to a new value or adds this element 224 * 225 * @param theForm {Node} the form to which the element has to be set to 226 * @param context the current request context 227 */ 228 _applyJSFArtifactValueToForm: function (context, theForm, value, identifier) { 229 230 if (!theForm) return; 231 var _Lang = this._Lang; 232 var _Dom = this._Dom; 233 var prefix = this._getPrefix(context); 234 235 //in IE7 looking up form elements with complex names (such as 'jakarta.faces.ViewState') fails in certain cases 236 //iterate through the form elements to find the element, instead 237 var fieldsFound = []; 238 239 var elements = theForm.elements; 240 for (var i = 0, l = elements.length; i < l; i++) { 241 var e = elements[i]; 242 //https://issues.apache.org/jira/browse/MYFACES-4230 243 //ie11 has a deviation from the standard behavior, we have to remap the null/undefined name 244 //to an empty string 245 var eName = e.name || ""; 246 247 if (eName.indexOf(identifier) != -1) { 248 fieldsFound.push(e); 249 } 250 } 251 252 if (fieldsFound.length) { 253 _Lang.arrForEach(fieldsFound, function (fieldFound) { 254 _Dom.setAttribute(fieldFound, "value", value); 255 }); 256 } else { 257 var element = this._Dom.getDummyPlaceHolder(); 258 259 //per JSF 2.3 spec the identifier of the element must be unique in the dom tree 260 //otherwise we will break the html spec here 261 element.innerHTML = ["<input type='hidden'", "id='", this._fetchUniqueId(prefix, identifier), "' name='", identifier, "' value='", value, "' />"].join(""); 262 //now we go to proper dom handling after having to deal with another ie screw-up 263 try { 264 theForm.appendChild(element.childNodes[0]); 265 } finally { 266 element.innerHTML = ""; 267 } 268 } 269 }, 270 271 _fetchUniqueId: function(prefix, identifier) { 272 var cnt = 0; 273 var retVal = prefix + identifier + jsf.separatorchar + cnt; 274 while(this._Dom.byId(retVal) != null) { 275 cnt++; 276 retVal = prefix + identifier + jsf.separatorchar + cnt; 277 } 278 return retVal; 279 }, 280 281 /** 282 * updates/inserts the jsf client artifacts under a given viewroot element 283 * 284 * @param context the client context holding all request context data and some internal data 285 * @param elem the root to start with, must be a dom node not an identifier 286 * @param value the new value 287 * @param identifier the identifier for the client artifact aka jakarta.faces.ViewState, ClientWindowId etc... 288 * 289 * @private 290 */ 291 _updateJSFClientArtifacts: function (context, value, identifier) { 292 293 //elem not found for whatever reason 294 //https://issues.apache.org/jira/browse/MYFACES-3544 295 296 var prefix = this._getPrefix(context); 297 298 //do we still need the issuing form update? I guess it is needed. 299 //jsf spec 2.3 and earlier all issuing forms must update 300 var sourceForm = (context._mfInternal._mfSourceFormId) ? this._Dom.byId(context._mfInternal._mfSourceFormId) : null; 301 if (sourceForm) { 302 sourceForm = this._Dom.byId(sourceForm); 303 if (sourceForm) { 304 //some cases where the source form cannot be updated 305 //because it is gone 306 this._applyJSFArtifactValueToForm(context, sourceForm, value, identifier); 307 } 308 } 309 310 311 312 var viewRoot = this._getViewRoot(context); 313 var forms = this._Dom.findByTagNames(viewRoot, {"form": 1}) || []; 314 315 //since the spec thanks to the over intrusive portlet api still is broken 316 //we need our old fallback hack for proper handling without having 317 //to deal with multiple render targets. 318 319 320 if(this._RT.getLocalOrGlobalConfig(context, "no_portlet_env", false)) { 321 322 //We update all elements under viewroot 323 //this clearly violates the jsf 2.3 jsdocs 324 //however I think that the jsdocs were sloppily updated 325 //because just updating the render targets under one viewroot and the issuing form 326 //again would leave broken viewstates, in the end the portlet spec is at fault here 327 //which came late to the game and expected all frameworks to adapt to their needs. 328 //instead of properly adapting to the frameworks 329 //now the viewroot mechanism per se would work, but people are dropping 330 //jsf 2.3 into old portlet containers which then expose the legacy behavior 331 //of having just one view root. 332 this._Lang.arrForEach(forms, this._Lang.hitch(this, function (elem) { 333 //update all forms which start with prefix (all render and execute targets 334 this._applyJSFArtifactValueToForm(context, elem, value, identifier); 335 })); 336 } else { 337 338 339 //check for a portlet condition a present viewroot 340 341 var viewRootId = viewRoot.id || ""; 342 343 for(var cnt = 0; cnt < context._mfInternal._updateForms.length; cnt++) { 344 var updateForm = context._mfInternal._updateForms[cnt]; 345 346 //follow the spec 2.3 path 1:1 we update the forms hosting the render targets which start 347 //with the viewroot 348 //if there is a viewroot present, however we seem to have a bug in myfaces 349 //even if we have a naming container response we 350 //cannot rely on the naming container being prefixed 351 352 //This atm is not bad, because we safely can assume 353 //that if no viewroot can be found we are under 354 //one single viewroot and can omit the prefix check 355 //(aka fallback into the old behavior) 356 357 358 if(updateForm.indexOf(viewRootId) != 0) { 359 continue; 360 } else { //either an empty viewroot, or a namespace match 361 this._applyJSFArtifactValueToForm(context, this._Dom.byId(updateForm), value, identifier); 362 } 363 } 364 365 } 366 367 }, 368 369 _getViewRoot: function (context) { 370 var prefix = this._getPrefix(context); 371 if (prefix == "") { 372 return document.getElementsByTagName("body")[0]; 373 } 374 prefix = prefix.substr(0, prefix.length - 1); 375 var viewRoot = document.getElementById(prefix); 376 if (viewRoot) { 377 return viewRoot; 378 } 379 return document.getElementsByTagName("body")[0]; 380 }, 381 382 383 _getPrefix: function (context) { 384 var mfInternal = context._mfInternal; 385 var prefix = mfInternal.namingModeId; 386 if (prefix != "") { 387 prefix = prefix + jsf.separatorchar; 388 } 389 return prefix; 390 }, 391 392 /** 393 * processes an incoming error from the response 394 * which is hosted under the <error> tag 395 * @param request the current request 396 * @param context the contect object 397 * @param node the node in the xml hosting the error message 398 */ 399 processError: function (request, context, node) { 400 /** 401 * <error> 402 * <error-name>String</error-name> 403 * <error-message><![CDATA[message]]></error-message> 404 * <error> 405 */ 406 var errorName = node.firstChild.textContent || node.firstChild.text || "", 407 errorMessage = node.childNodes[1].firstChild.data || ""; 408 409 this.attr("impl").sendError(request, context, this.attr("impl").SERVER_ERROR, errorName, errorMessage, "myfaces._impl.xhrCore._AjaxResponse", "processError"); 410 }, 411 412 /** 413 * processes an incoming xml redirect directive from the ajax response 414 * @param request the request object 415 * @param context the context 416 * @param node the node hosting the redirect data 417 */ 418 processRedirect: function (request, context, node) { 419 /** 420 * <redirect url="url to redirect" /> 421 */ 422 var _Lang = this._Lang; 423 var redirectUrl = node.getAttribute("url"); 424 if (!redirectUrl) { 425 throw this._raiseError(new Error(), _Lang.getMessage("ERR_RED_URL", null, "_AjaxResponse.processRedirect"), "processRedirect"); 426 } 427 redirectUrl = _Lang.trim(redirectUrl); 428 if (redirectUrl == "") { 429 return false; 430 } 431 window.location = redirectUrl; 432 return true; 433 } 434 , 435 436 /** 437 * main entry point for processing the changes 438 * it deals with the <changes> node of the 439 * response 440 * 441 * @param request the xhr request object 442 * @param context the context map 443 * @param node the changes node to be processed 444 */ 445 processChanges: function (request, context, node) { 446 var changes = node.childNodes; 447 var _Lang = this._Lang; 448 //note we need to trace the changes which could affect our insert update or delete 449 //se that we can realign our ViewStates afterwards 450 //the realignment must happen post change processing 451 452 for (var i = 0; i < changes.length; i++) { 453 454 switch (changes[i].tagName) { 455 456 case this.CMD_UPDATE: 457 this.processUpdate(request, context, changes[i]); 458 break; 459 case this.CMD_EVAL: 460 _Lang.globalEval(changes[i].firstChild.data); 461 break; 462 case this.CMD_INSERT: 463 this.processInsert(request, context, changes[i]); 464 break; 465 case this.CMD_DELETE: 466 this.processDelete(request, context, changes[i]); 467 break; 468 case this.CMD_ATTRIBUTES: 469 this.processAttributes(request, context, changes[i]); 470 break; 471 case this.CMD_EXTENSION: 472 break; 473 case undefined: 474 // ignoring white spaces 475 break; 476 default: 477 throw this._raiseError(new Error(), "_AjaxResponse.processChanges: Illegal Command Issued", "processChanges"); 478 } 479 } 480 481 return true; 482 }, 483 484 /** 485 * First sub-step process a pending update tag 486 * 487 * @param request the xhr request object 488 * @param context the context map 489 * @param node the changes node to be processed 490 */ 491 processUpdate: function (request, context, node) { 492 var mfInternal = context._mfInternal; 493 if ((node.getAttribute('id').indexOf(this.P_VIEWSTATE) != -1) || (node.getAttribute('id').indexOf(this.P_CLIENTWINDOW) != -1)) { 494 if (node.getAttribute('id').indexOf(this.P_VIEWSTATE) != -1) { 495 mfInternal.appliedViewState = this._Dom.concatCDATABlocks(node);//node.firstChild.nodeValue; 496 } else if (node.getAttribute('id').indexOf(this.P_CLIENTWINDOW) != -1) { 497 mfInternal.appliedClientWindow = node.firstChild.nodeValue; 498 } 499 } 500 else { 501 // response may contain several blocks 502 var cDataBlock = this._Dom.concatCDATABlocks(node), 503 resultNode = null, 504 pushOpRes = this._Lang.hitch(this, this._pushOperationResult); 505 506 switch (node.getAttribute('id')) { 507 case this.P_VIEWROOT: 508 509 cDataBlock = cDataBlock.substring(cDataBlock.indexOf("<html")); 510 511 var parsedData = this._replaceHead(request, context, cDataBlock); 512 513 ('undefined' != typeof parsedData && null != parsedData) ? this._replaceBody(request, context, cDataBlock, parsedData) : this._replaceBody(request, context, cDataBlock); 514 515 break; 516 case this.P_VIEWHEAD: 517 //we cannot replace the head, almost no browser allows this, some of them throw errors 518 //others simply ignore it or replace it and destroy the dom that way! 519 this._replaceHead(request, context, cDataBlock); 520 521 break; 522 case this.P_VIEWBODY: 523 //we assume the cdata block is our body including the tag 524 resultNode = this._replaceBody(request, context, cDataBlock); 525 if (resultNode) { 526 pushOpRes(context, resultNode); 527 } 528 break; 529 case this.P_RESOURCE: 530 531 this._addResourceToHead(request, context, cDataBlock); 532 break; 533 default: 534 535 resultNode = this.replaceHtmlItem(request, context, node.getAttribute('id'), cDataBlock); 536 if (resultNode) { 537 pushOpRes(context, resultNode); 538 } 539 break; 540 } 541 } 542 543 return true; 544 }, 545 546 _pushOperationResult: function(context, resultNode) { 547 var mfInternal = context._mfInternal; 548 var pushSubnode = this._Lang.hitch(this, function(currNode) { 549 var parentForm = this._Dom.getParent(currNode, "form"); 550 //if possible we work over the ids 551 //so that elements later replaced are referenced 552 //at the latest possibility 553 if (null != parentForm) { 554 mfInternal._updateForms.push(parentForm.id || parentForm); 555 } 556 else { 557 mfInternal._updateElems.push(currNode.id || currNode); 558 } 559 }); 560 561 var pushEmbedded = this._Lang.hitch(this, function(currNode) { 562 if(currNode.tagName && this._Lang.equalsIgnoreCase(currNode.tagName, "form")) { 563 if(currNode.id) { //should not happen but just in case someone manipulates the html 564 mfInternal._updateForms.push(currNode.id); 565 } 566 } else { 567 var childForms = this._Dom.findByTagName(currNode, "form"); 568 if(childForms && childForms.length) { 569 for(var cnt = 0; cnt < childForms.length; cnt++) { 570 if(childForms[cnt].id) { 571 mfInternal._updateForms.push(childForms[cnt].id); 572 } 573 } 574 } 575 } 576 577 }); 578 579 580 var isArr = 'undefined' != typeof resultNode.length && 'undefined' == typeof resultNode.nodeType; 581 if (isArr && resultNode.length) { 582 for (var cnt = 0; cnt < resultNode.length; cnt++) { 583 pushSubnode(resultNode[cnt]); 584 pushEmbedded(resultNode[cnt]); 585 } 586 } else if (!isArr) { 587 pushSubnode(resultNode); 588 pushEmbedded(resultNode); 589 } 590 591 }, 592 593 594 /** 595 * replaces a current head theoretically, 596 * pratically only the scripts are evaled anew since nothing else 597 * can be changed. 598 * 599 * @param request the current request 600 * @param context the ajax context 601 * @param newData the data to be processed 602 * 603 * @return an xml representation of the page for further processing if possible 604 */ 605 _replaceHead: function (request, context, newData) { 606 607 var _Lang = this._Lang, 608 _Dom = this._Dom, 609 isWebkit = this._RT.browser.isWebKit, 610 //we have to work around an xml parsing bug in Webkit 611 //see https://issues.apache.org/jira/browse/MYFACES-3061 612 doc = (!isWebkit) ? _Lang.parseXML(newData) : null, 613 newHead = null; 614 615 if (!isWebkit && _Lang.isXMLParseError(doc)) { 616 doc = _Lang.parseXML(newData.replace(/<!\-\-[\s\n]*<!\-\-/g, "<!--").replace(/\/\/-->[\s\n]*\/\/-->/g, "//-->")); 617 } 618 619 if (isWebkit || _Lang.isXMLParseError(doc)) { 620 //the standard xml parser failed we retry with the stripper 621 var parser = new (this._RT.getGlobalConfig("updateParser", myfaces._impl._util._HtmlStripper))(); 622 var headData = parser.parse(newData, "head"); 623 //We cannot avoid it here, but we have reduced the parsing now down to the bare minimum 624 //for further processing 625 newHead = _Lang.parseXML("<head>" + headData + "</head>"); 626 //last and slowest option create a new head element and let the browser 627 //do its slow job 628 if (_Lang.isXMLParseError(newHead)) { 629 try { 630 newHead = _Dom.createElement("head"); 631 newHead.innerHTML = headData; 632 } catch (e) { 633 //we give up no further fallbacks 634 throw this._raiseError(new Error(), "Error head replacement failed reason:" + e.toString(), "_replaceHead"); 635 } 636 } 637 } else { 638 //parser worked we go on 639 newHead = doc.getElementsByTagName("head")[0]; 640 } 641 642 var oldTags = _Dom.findByTagNames(document.getElementsByTagName("head")[0], {"link": true, "style": true}); 643 _Dom.runCss(newHead, true); 644 _Dom.deleteItems(oldTags); 645 646 //var oldTags = _Dom.findByTagNames(document.getElementsByTagName("head")[0], {"script": true}); 647 //_Dom.deleteScripts(oldTags); 648 _Dom.runScripts(newHead, true); 649 650 return doc; 651 }, 652 653 _addResourceToHead: function (request, context, newData) { 654 var lastHeadChildTag = document.getElementsByTagName("head")[0].lastChild; 655 656 this._Dom.insertAfter(lastHeadChildTag, newData); 657 658 }, 659 660 /** 661 * special method to handle the body dom manipulation, 662 * replacing the entire body does not work fully by simply adding a second body 663 * and by creating a range instead we have to work around that by dom creating a second 664 * body and then filling it properly! 665 * 666 * @param {Object} request our request object 667 * @param {Object} context (Map) the response context 668 * @param {String} newData the markup which replaces the old dom node! 669 * @param {Node} parsedData (optional) preparsed XML representation data of the current document 670 */ 671 _replaceBody: function (request, context, newData /*varargs*/) { 672 var _RT = this._RT, 673 _Dom = this._Dom, 674 _Lang = this._Lang, 675 676 oldBody = document.getElementsByTagName("body")[0], 677 placeHolder = document.createElement("div"), 678 isWebkit = _RT.browser.isWebKit; 679 680 placeHolder.id = "myfaces_bodyplaceholder"; 681 682 _Dom._removeChildNodes(oldBody); 683 oldBody.innerHTML = ""; 684 oldBody.appendChild(placeHolder); 685 686 var bodyData, doc = null, parser; 687 688 //we have to work around an xml parsing bug in Webkit 689 //see https://issues.apache.org/jira/browse/MYFACES-3061 690 if (!isWebkit) { 691 doc = (arguments.length > 3) ? arguments[3] : _Lang.parseXML(newData); 692 } 693 694 if (!isWebkit && _Lang.isXMLParseError(doc)) { 695 doc = _Lang.parseXML(newData.replace(/<!\-\-[\s\n]*<!\-\-/g, "<!--").replace(/\/\/-->[\s\n]*\/\/-->/g, "//-->")); 696 } 697 698 if (isWebkit || _Lang.isXMLParseError(doc)) { 699 //the standard xml parser failed we retry with the stripper 700 701 parser = new (_RT.getGlobalConfig("updateParser", myfaces._impl._util._HtmlStripper))(); 702 703 bodyData = parser.parse(newData, "body"); 704 } else { 705 //parser worked we go on 706 var newBodyData = doc.getElementsByTagName("body")[0]; 707 708 //speedwise we serialize back into the code 709 //for code reduction, speedwise we will take a small hit 710 //there which we will clean up in the future, but for now 711 //this is ok, I guess, since replace body only is a small subcase 712 //bodyData = _Lang.serializeChilds(newBodyData); 713 var browser = _RT.browser; 714 if (!browser.isIEMobile || browser.isIEMobile >= 7) { 715 //TODO check what is failing there 716 for (var cnt = 0; cnt < newBodyData.attributes.length; cnt++) { 717 var value = newBodyData.attributes[cnt].value; 718 if (value) 719 _Dom.setAttribute(oldBody, newBodyData.attributes[cnt].name, value); 720 } 721 } 722 } 723 //we cannot serialize here, due to escape problems 724 //we must parse, this is somewhat unsafe but should be safe enough 725 parser = new (_RT.getGlobalConfig("updateParser", myfaces._impl._util._HtmlStripper))(); 726 bodyData = parser.parse(newData, "body"); 727 728 var returnedElement = this.replaceHtmlItem(request, context, placeHolder, bodyData); 729 730 if (returnedElement) { 731 this._pushOperationResult(context, returnedElement); 732 } 733 return returnedElement; 734 }, 735 736 /** 737 * Replaces HTML elements through others and handle errors if the occur in the replacement part 738 * 739 * @param {Object} request (xhrRequest) 740 * @param {Object} context (Map) 741 * @param {Object} itemIdToReplace (String|Node) - ID of the element to replace 742 * @param {String} markup - the new tag 743 */ 744 replaceHtmlItem: function (request, context, itemIdToReplace, markup) { 745 var _Lang = this._Lang, _Dom = this._Dom; 746 747 var item = (!_Lang.isString(itemIdToReplace)) ? itemIdToReplace : 748 _Dom.byIdOrName(itemIdToReplace); 749 750 if (!item) { 751 throw this._raiseError(new Error(), _Lang.getMessage("ERR_ITEM_ID_NOTFOUND", null, "_AjaxResponse.replaceHtmlItem", (itemIdToReplace) ? itemIdToReplace.toString() : "undefined"), "replaceHtmlItem"); 752 } 753 return _Dom.outerHTML(item, markup, this._RT.getLocalOrGlobalConfig(context, "preserveFocus", false)); 754 }, 755 756 /** 757 * xml insert command handler 758 * 759 * @param request the ajax request element 760 * @param context the context element holding the data 761 * @param node the xml node holding the insert data 762 * @return true upon successful completion, false otherwise 763 * 764 **/ 765 processInsert: function (request, context, node) { 766 /*remapping global namespaces for speed and readability reasons*/ 767 var _Dom = this._Dom, 768 _Lang = this._Lang, 769 //determine which path to go: 770 insertData = this._parseInsertData(request, context, node); 771 772 if (!insertData) return false; 773 774 var opNode = _Dom.byIdOrName(insertData.opId); 775 if (!opNode) { 776 throw this._raiseError(new Error(), _Lang.getMessage("ERR_PPR_INSERTBEFID_1", null, "_AjaxResponse.processInsert", insertData.opId), "processInsert"); 777 } 778 779 //call insertBefore or insertAfter in our dom routines 780 var replacementFragment = _Dom[insertData.insertType](opNode, insertData.cDataBlock); 781 if (replacementFragment) { 782 this._pushOperationResult(context, replacementFragment); 783 } 784 return true; 785 }, 786 787 /** 788 * determines the corner data from the insert tag parsing process 789 * 790 * 791 * @param request request 792 * @param context context 793 * @param node the current node pointing to the insert tag 794 * @return false if the parsing failed, otherwise a map with follwing attributes 795 * <ul> 796 * <li>inserType - a ponter to a constant which maps the direct function name for the insert operation </li> 797 * <li>opId - the before or after id </li> 798 * <li>cDataBlock - the html cdata block which needs replacement </li> 799 * </ul> 800 * 801 * TODO we have to find a mechanism to replace the direct sendError calls with a javascript exception 802 * which we then can use for cleaner error code handling 803 */ 804 _parseInsertData: function (request, context, node) { 805 var _Lang = this._Lang, 806 _Dom = this._Dom, 807 concatCDATA = _Dom.concatCDATABlocks, 808 809 INSERT_TYPE_BEFORE = "insertBefore", 810 INSERT_TYPE_AFTER = "insertAfter", 811 812 id = node.getAttribute("id"), 813 beforeId = node.getAttribute("before"), 814 afterId = node.getAttribute("after"), 815 ret = {}; 816 817 //now we have to make a distinction between two different parsing paths 818 //due to a spec malalignment 819 //a <insert id="... beforeId|AfterId ="... 820 //b <insert><before id="..., <insert> <after id=".... 821 //see https://issues.apache.org/jira/browse/MYFACES-3318 822 //simple id, case1 823 if (id && beforeId && !afterId) { 824 ret.insertType = INSERT_TYPE_BEFORE; 825 ret.opId = beforeId; 826 ret.cDataBlock = concatCDATA(node); 827 828 //<insert id=".. afterId=".. 829 } else if (id && !beforeId && afterId) { 830 ret.insertType = INSERT_TYPE_AFTER; 831 ret.opId = afterId; 832 ret.cDataBlock = concatCDATA(node); 833 834 //<insert><before id="... <insert><after id="... 835 } else if (!id) { 836 var opType = node.childNodes[0].tagName; 837 838 if (opType != "before" && opType != "after") { 839 throw this._raiseError(new Error(), _Lang.getMessage("ERR_PPR_INSERTBEFID"), "_parseInsertData"); 840 } 841 opType = opType.toLowerCase(); 842 var beforeAfterId = node.childNodes[0].getAttribute("id"); 843 ret.insertType = (opType == "before") ? INSERT_TYPE_BEFORE : INSERT_TYPE_AFTER; 844 ret.opId = beforeAfterId; 845 ret.cDataBlock = concatCDATA(node.childNodes[0]); 846 } else { 847 throw this._raiseError(new Error(), [_Lang.getMessage("ERR_PPR_IDREQ"), 848 "\n ", 849 _Lang.getMessage("ERR_PPR_INSERTBEFID")].join(""), "_parseInsertData"); 850 } 851 ret.opId = _Lang.trim(ret.opId); 852 return ret; 853 }, 854 855 processDelete: function (request, context, node) { 856 857 var _Lang = this._Lang, 858 _Dom = this._Dom, 859 deleteId = node.getAttribute('id'); 860 861 if (!deleteId) { 862 throw this._raiseError(new Error(), _Lang.getMessage("ERR_PPR_UNKNOWNCID", null, "_AjaxResponse.processDelete", ""), "processDelete"); 863 } 864 865 var item = _Dom.byIdOrName(deleteId); 866 if (!item) { 867 throw this._raiseError(new Error(), _Lang.getMessage("ERR_PPR_UNKNOWNCID", null, "_AjaxResponse.processDelete", deleteId), "processDelete"); 868 } 869 870 var parentForm = this._Dom.getParent(item, "form"); 871 if (null != parentForm) { 872 context._mfInternal._updateForms.push(parentForm); 873 } 874 _Dom.deleteItem(item); 875 876 return true; 877 }, 878 879 processAttributes: function (request, context, node) { 880 //we now route into our attributes function to bypass 881 //IE quirks mode incompatibilities to the biggest possible extent 882 //most browsers just have to do a setAttributes but IE 883 //behaves as usual not like the official standard 884 //myfaces._impl._util.this._Dom.setAttribute(domNode, attribute, value; 885 886 var _Lang = this._Lang, 887 //<attributes id="id of element"> <attribute name="attribute name" value="attribute value" />* </attributes> 888 elemId = node.getAttribute('id'); 889 890 if (!elemId) { 891 throw this._raiseError(new Error(), "Error in attributes, id not in xml markup", "processAttributes"); 892 } 893 var childNodes = node.childNodes; 894 895 if (!childNodes) { 896 return false; 897 } 898 for (var loop2 = 0; loop2 < childNodes.length; loop2++) { 899 var attributesNode = childNodes[loop2], 900 attrName = attributesNode.getAttribute("name"), 901 attrValue = attributesNode.getAttribute("value"); 902 903 if (!attrName) { 904 continue; 905 } 906 907 attrName = _Lang.trim(attrName); 908 /*no value means reset*/ 909 //value can be of boolean value hence full check 910 if ('undefined' == typeof attrValue || null == attrValue) { 911 attrValue = ""; 912 } 913 914 switch (elemId) { 915 case this.P_VIEWROOT: 916 throw this._raiseError(new Error(), _Lang.getMessage("ERR_NO_VIEWROOTATTR", null, "_AjaxResponse.processAttributes"), "processAttributes"); 917 918 case this.P_VIEWHEAD: 919 throw this._raiseError(new Error(), _Lang.getMessage("ERR_NO_HEADATTR", null, "_AjaxResponse.processAttributes"), "processAttributes"); 920 921 case this.P_VIEWBODY: 922 var element = document.getElementsByTagName("body")[0]; 923 this._Dom.setAttribute(element, attrName, attrValue); 924 break; 925 926 default: 927 this._Dom.setAttribute(document.getElementById(elemId), attrName, attrValue); 928 break; 929 } 930 } 931 return true; 932 }, 933 934 /** 935 * internal helper which raises an error in the 936 * format we need for further processing 937 * 938 * @param message the message 939 * @param title the title of the error (optional) 940 * @param name the name of the error (optional) 941 */ 942 _raiseError: function (error, message, caller, title, name) { 943 var _Impl = this.attr("impl"); 944 var finalTitle = title || _Impl.MALFORMEDXML; 945 var finalName = name || _Impl.MALFORMEDXML; 946 var finalMessage = message || ""; 947 948 return this._Lang.makeException(error, finalTitle, finalName, this._nameSpace, caller || ( (arguments.caller) ? arguments.caller.toString() : "_raiseError"), finalMessage); 949 } 950 }); 951