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: "javax.faces.ViewState", 53 P_CLIENTWINDOW: "javax.faces.ClientWindow", 54 P_VIEWROOT: "javax.faces.ViewRoot", 55 P_VIEWHEAD: "javax.faces.ViewHead", 56 P_VIEWBODY: "javax.faces.ViewBody", 57 P_RESOURCE: "javax.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 'javax.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 if (e.name.indexOf(identifier) != -1) { 243 fieldsFound.push(e); 244 } 245 } 246 247 if (fieldsFound.length) { 248 _Lang.arrForEach(fieldsFound, function (fieldFound) { 249 _Dom.setAttribute(fieldFound, "value", value); 250 }); 251 } else { 252 var element = this._Dom.getDummyPlaceHolder(); 253 254 //per JSF 2.3 spec the identifier of the element must be unique in the dom tree 255 //otherwise we will break the html spec here 256 element.innerHTML = ["<input type='hidden'", "id='", this._fetchUniqueId(prefix, identifier), "' name='", identifier, "' value='", value, "' />"].join(""); 257 //now we go to proper dom handling after having to deal with another ie screwup 258 try { 259 theForm.appendChild(element.childNodes[0]); 260 } finally { 261 element.innerHTML = ""; 262 } 263 } 264 }, 265 266 _fetchUniqueId: function(prefix, identifier) { 267 var cnt = 0; 268 var retVal = prefix + identifier + jsf.separatorchar + cnt; 269 while(this._Dom.byId(retVal) != null) { 270 cnt++; 271 retVal = prefix + identifier + jsf.separatorchar + cnt; 272 } 273 return retVal; 274 }, 275 276 /** 277 * updates/inserts the jsf client artifacts under a given viewroot element 278 * 279 * @param context the client context holding all request context data and some internal data 280 * @param elem the root to start with, must be a dom node not an identifier 281 * @param value the new value 282 * @param identifier the identifier for the client artifact aka javax.faces.ViewState, ClientWindowId etc... 283 * 284 * @private 285 */ 286 _updateJSFClientArtifacts: function (context, value, identifier) { 287 288 //elem not found for whatever reason 289 //https://issues.apache.org/jira/browse/MYFACES-3544 290 291 var prefix = this._getPrefix(context); 292 293 //do we still need the issuing form update? I guess it is needed. 294 var sourceForm = (context._mfInternal._mfSourceFormId) ? this._Dom.byId(context._mfInternal._mfSourceFormId) : null; 295 if (sourceForm) { 296 sourceForm = this._Dom.byId(sourceForm); 297 if (sourceForm) { 298 //some cases where the source form cannot be updated 299 //because it is gone 300 this._applyJSFArtifactValueToForm(context, sourceForm, value, identifier); 301 } 302 } 303 304 305 var viewRoot = this._getViewRoot(context); 306 var forms = this._Dom.findByTagNames(viewRoot, {"form": 1}) || []; 307 308 //since the spec thanks to the over intrusive portlet api still is broken 309 //we need our old fallback hack for proper handling without having 310 //to deal with multiple render targets. 311 if(this._RT.getLocalOrGlobalConfig(context, "no_portlet_env", false)) { 312 313 //We update all elements under viewroot 314 //this clearly violates the jsf 2.3 jsdocs 315 //however I think that the jsdocs were sloppily updated 316 //because just updating the render targets under one viewroot and the issuing form 317 //again would leave broken viewstates, in the end the portlet spec is at fault here 318 //which came late to the game and expected all frameworks to adapt to their needs. 319 //instead of properly adapting to the frameworks 320 //now the viewroot mechanism per se would work, but people are dropping 321 //jsf 2.3 into old portlet containers which then expose the legacy behavior 322 //of having just one view root. 323 this._Lang.arrForEach(forms, this._Lang.hitch(this, function (elem) { 324 //update all forms which start with prefix (all render and execute targets 325 this._applyJSFArtifactValueToForm(context, elem, value, identifier); 326 })); 327 } else { 328 329 //follow the spec 2.3 path 1:1 we update the forms hosting the render targets which start 330 //with the viewroot 331 for(var cnt = 0; cnt < context._mfInternal._updateForms.length; cnt++) { 332 var updateForm = context._mfInternal._updateForms[cnt]; 333 var viewRootId = viewRoot.id || ""; 334 if(updateForm.indexOf(viewRootId) != 0) { 335 continue; 336 } else { 337 this._applyJSFArtifactValueToForm(context, this._Dom.byId(updateForm), value, identifier); 338 } 339 } 340 341 } 342 343 }, 344 345 _getViewRoot: function (context) { 346 var prefix = this._getPrefix(context); 347 if (prefix == "") { 348 return document.getElementsByTagName("body")[0]; 349 } 350 prefix = prefix.substr(0, prefix.length - 1); 351 var viewRoot = document.getElementById(prefix); 352 if (viewRoot) { 353 return viewRoot; 354 } 355 return document.getElementsByTagName("body")[0]; 356 }, 357 358 359 _getPrefix: function (context) { 360 var mfInternal = context._mfInternal; 361 var prefix = mfInternal.namingModeId; 362 if (prefix != "") { 363 prefix = prefix + jsf.separatorchar; 364 } 365 return prefix; 366 }, 367 368 /** 369 * processes an incoming error from the response 370 * which is hosted under the <error> tag 371 * @param request the current request 372 * @param context the contect object 373 * @param node the node in the xml hosting the error message 374 */ 375 processError: function (request, context, node) { 376 /** 377 * <error> 378 * <error-name>String</error-name> 379 * <error-message><![CDATA[message]]></error-message> 380 * <error> 381 */ 382 var errorName = node.firstChild.textContent || node.firstChild.text || "", 383 errorMessage = node.childNodes[1].firstChild.data || ""; 384 385 this.attr("impl").sendError(request, context, this.attr("impl").SERVER_ERROR, errorName, errorMessage, "myfaces._impl.xhrCore._AjaxResponse", "processError"); 386 }, 387 388 /** 389 * processes an incoming xml redirect directive from the ajax response 390 * @param request the request object 391 * @param context the context 392 * @param node the node hosting the redirect data 393 */ 394 processRedirect: function (request, context, node) { 395 /** 396 * <redirect url="url to redirect" /> 397 */ 398 var _Lang = this._Lang; 399 var redirectUrl = node.getAttribute("url"); 400 if (!redirectUrl) { 401 throw this._raiseError(new Error(), _Lang.getMessage("ERR_RED_URL", null, "_AjaxResponse.processRedirect"), "processRedirect"); 402 } 403 redirectUrl = _Lang.trim(redirectUrl); 404 if (redirectUrl == "") { 405 return false; 406 } 407 window.location = redirectUrl; 408 return true; 409 } 410 , 411 412 /** 413 * main entry point for processing the changes 414 * it deals with the <changes> node of the 415 * response 416 * 417 * @param request the xhr request object 418 * @param context the context map 419 * @param node the changes node to be processed 420 */ 421 processChanges: function (request, context, node) { 422 var changes = node.childNodes; 423 var _Lang = this._Lang; 424 //note we need to trace the changes which could affect our insert update or delete 425 //se that we can realign our ViewStates afterwards 426 //the realignment must happen post change processing 427 428 for (var i = 0; i < changes.length; i++) { 429 430 switch (changes[i].tagName) { 431 432 case this.CMD_UPDATE: 433 this.processUpdate(request, context, changes[i]); 434 break; 435 case this.CMD_EVAL: 436 _Lang.globalEval(changes[i].firstChild.data); 437 break; 438 case this.CMD_INSERT: 439 this.processInsert(request, context, changes[i]); 440 break; 441 case this.CMD_DELETE: 442 this.processDelete(request, context, changes[i]); 443 break; 444 case this.CMD_ATTRIBUTES: 445 this.processAttributes(request, context, changes[i]); 446 break; 447 case this.CMD_EXTENSION: 448 break; 449 case undefined: 450 // ignoring white spaces 451 break; 452 default: 453 throw this._raiseError(new Error(), "_AjaxResponse.processChanges: Illegal Command Issued", "processChanges"); 454 } 455 } 456 457 return true; 458 }, 459 460 /** 461 * First sub-step process a pending update tag 462 * 463 * @param request the xhr request object 464 * @param context the context map 465 * @param node the changes node to be processed 466 */ 467 processUpdate: function (request, context, node) { 468 var mfInternal = context._mfInternal; 469 if ((node.getAttribute('id').indexOf(this.P_VIEWSTATE) != -1) || (node.getAttribute('id').indexOf(this.P_CLIENTWINDOW) != -1)) { 470 if (node.getAttribute('id').indexOf(this.P_VIEWSTATE) != -1) { 471 mfInternal.appliedViewState = this._Dom.concatCDATABlocks(node);//node.firstChild.nodeValue; 472 } else if (node.getAttribute('id').indexOf(this.P_CLIENTWINDOW) != -1) { 473 mfInternal.appliedClientWindow = node.firstChild.nodeValue; 474 } 475 } 476 else { 477 // response may contain several blocks 478 var cDataBlock = this._Dom.concatCDATABlocks(node), 479 resultNode = null, 480 pushOpRes = this._Lang.hitch(this, this._pushOperationResult); 481 482 switch (node.getAttribute('id')) { 483 case this.P_VIEWROOT: 484 485 cDataBlock = cDataBlock.substring(cDataBlock.indexOf("<html")); 486 487 var parsedData = this._replaceHead(request, context, cDataBlock); 488 489 ('undefined' != typeof parsedData && null != parsedData) ? this._replaceBody(request, context, cDataBlock, parsedData) : this._replaceBody(request, context, cDataBlock); 490 491 break; 492 case this.P_VIEWHEAD: 493 //we cannot replace the head, almost no browser allows this, some of them throw errors 494 //others simply ignore it or replace it and destroy the dom that way! 495 this._replaceHead(request, context, cDataBlock); 496 497 break; 498 case this.P_VIEWBODY: 499 //we assume the cdata block is our body including the tag 500 resultNode = this._replaceBody(request, context, cDataBlock); 501 if (resultNode) { 502 pushOpRes(context, resultNode); 503 } 504 break; 505 case this.P_RESOURCE: 506 507 this._addResourceToHead(request, context, cDataBlock); 508 break; 509 default: 510 511 resultNode = this.replaceHtmlItem(request, context, node.getAttribute('id'), cDataBlock); 512 if (resultNode) { 513 pushOpRes(context, resultNode); 514 } 515 break; 516 } 517 } 518 519 return true; 520 }, 521 522 _pushOperationResult: function(context, resultNode) { 523 var mfInternal = context._mfInternal; 524 var pushSubnode = this._Lang.hitch(this, function(currNode) { 525 var parentForm = this._Dom.getParent(currNode, "form"); 526 //if possible we work over the ids 527 //so that elements later replaced are referenced 528 //at the latest possibility 529 if (null != parentForm) { 530 mfInternal._updateForms.push(parentForm.id || parentForm); 531 } 532 else { 533 mfInternal._updateElems.push(currNode.id || currNode); 534 } 535 }); 536 537 var pushEmbedded = this._Lang.hitch(this, function(currNode) { 538 if(currNode.tagName && currNode.tagName == "form") { 539 mfInternal._updateForms.push(currNode); 540 } else { 541 var childForms = this._Dom.findByTagName(currNode, "form"); 542 if(childForms && childForms.length) { 543 for(var cnt = 0; cnt < childForms.lenght; cnt++) { 544 mfInternal._updateForms.push(childForms[cnt]); 545 } 546 } 547 } 548 549 }); 550 551 552 var isArr = 'undefined' != typeof resultNode.length && 'undefined' == typeof resultNode.nodeType; 553 if (isArr && resultNode.length) { 554 for (var cnt = 0; cnt < resultNode.length; cnt++) { 555 pushSubnode(resultNode[cnt]); 556 pushEmbedded(resultNode[cnt]); 557 } 558 } else if (!isArr) { 559 pushSubnode(resultNode); 560 pushEmbedded(resultNode); 561 } 562 563 }, 564 565 566 /** 567 * replaces a current head theoretically, 568 * pratically only the scripts are evaled anew since nothing else 569 * can be changed. 570 * 571 * @param request the current request 572 * @param context the ajax context 573 * @param newData the data to be processed 574 * 575 * @return an xml representation of the page for further processing if possible 576 */ 577 _replaceHead: function (request, context, newData) { 578 579 var _Lang = this._Lang, 580 _Dom = this._Dom, 581 isWebkit = this._RT.browser.isWebKit, 582 //we have to work around an xml parsing bug in Webkit 583 //see https://issues.apache.org/jira/browse/MYFACES-3061 584 doc = (!isWebkit) ? _Lang.parseXML(newData) : null, 585 newHead = null; 586 587 if (!isWebkit && _Lang.isXMLParseError(doc)) { 588 doc = _Lang.parseXML(newData.replace(/<!\-\-[\s\n]*<!\-\-/g, "<!--").replace(/\/\/-->[\s\n]*\/\/-->/g, "//-->")); 589 } 590 591 if (isWebkit || _Lang.isXMLParseError(doc)) { 592 //the standard xml parser failed we retry with the stripper 593 var parser = new (this._RT.getGlobalConfig("updateParser", myfaces._impl._util._HtmlStripper))(); 594 var headData = parser.parse(newData, "head"); 595 //We cannot avoid it here, but we have reduced the parsing now down to the bare minimum 596 //for further processing 597 newHead = _Lang.parseXML("<head>" + headData + "</head>"); 598 //last and slowest option create a new head element and let the browser 599 //do its slow job 600 if (_Lang.isXMLParseError(newHead)) { 601 try { 602 newHead = _Dom.createElement("head"); 603 newHead.innerHTML = headData; 604 } catch (e) { 605 //we give up no further fallbacks 606 throw this._raiseError(new Error(), "Error head replacement failed reason:" + e.toString(), "_replaceHead"); 607 } 608 } 609 } else { 610 //parser worked we go on 611 newHead = doc.getElementsByTagName("head")[0]; 612 } 613 614 var oldTags = _Dom.findByTagNames(document.getElementsByTagName("head")[0], {"link": true, "style": true}); 615 _Dom.runCss(newHead, true); 616 _Dom.deleteItems(oldTags); 617 618 //var oldTags = _Dom.findByTagNames(document.getElementsByTagName("head")[0], {"script": true}); 619 //_Dom.deleteScripts(oldTags); 620 _Dom.runScripts(newHead, true); 621 622 return doc; 623 }, 624 625 _addResourceToHead: function (request, context, newData) { 626 var lastHeadChildTag = document.getElementsByTagName("head")[0].lastChild; 627 628 this._Dom.insertAfter(lastHeadChildTag, newData); 629 630 }, 631 632 /** 633 * special method to handle the body dom manipulation, 634 * replacing the entire body does not work fully by simply adding a second body 635 * and by creating a range instead we have to work around that by dom creating a second 636 * body and then filling it properly! 637 * 638 * @param {Object} request our request object 639 * @param {Object} context (Map) the response context 640 * @param {String} newData the markup which replaces the old dom node! 641 * @param {Node} parsedData (optional) preparsed XML representation data of the current document 642 */ 643 _replaceBody: function (request, context, newData /*varargs*/) { 644 var _RT = this._RT, 645 _Dom = this._Dom, 646 _Lang = this._Lang, 647 648 oldBody = document.getElementsByTagName("body")[0], 649 placeHolder = document.createElement("div"), 650 isWebkit = _RT.browser.isWebKit; 651 652 placeHolder.id = "myfaces_bodyplaceholder"; 653 654 _Dom._removeChildNodes(oldBody); 655 oldBody.innerHTML = ""; 656 oldBody.appendChild(placeHolder); 657 658 var bodyData, doc = null, parser; 659 660 //we have to work around an xml parsing bug in Webkit 661 //see https://issues.apache.org/jira/browse/MYFACES-3061 662 if (!isWebkit) { 663 doc = (arguments.length > 3) ? arguments[3] : _Lang.parseXML(newData); 664 } 665 666 if (!isWebkit && _Lang.isXMLParseError(doc)) { 667 doc = _Lang.parseXML(newData.replace(/<!\-\-[\s\n]*<!\-\-/g, "<!--").replace(/\/\/-->[\s\n]*\/\/-->/g, "//-->")); 668 } 669 670 if (isWebkit || _Lang.isXMLParseError(doc)) { 671 //the standard xml parser failed we retry with the stripper 672 673 parser = new (_RT.getGlobalConfig("updateParser", myfaces._impl._util._HtmlStripper))(); 674 675 bodyData = parser.parse(newData, "body"); 676 } else { 677 //parser worked we go on 678 var newBodyData = doc.getElementsByTagName("body")[0]; 679 680 //speedwise we serialize back into the code 681 //for code reduction, speedwise we will take a small hit 682 //there which we will clean up in the future, but for now 683 //this is ok, I guess, since replace body only is a small subcase 684 //bodyData = _Lang.serializeChilds(newBodyData); 685 var browser = _RT.browser; 686 if (!browser.isIEMobile || browser.isIEMobile >= 7) { 687 //TODO check what is failing there 688 for (var cnt = 0; cnt < newBodyData.attributes.length; cnt++) { 689 var value = newBodyData.attributes[cnt].value; 690 if (value) 691 _Dom.setAttribute(oldBody, newBodyData.attributes[cnt].name, value); 692 } 693 } 694 } 695 //we cannot serialize here, due to escape problems 696 //we must parse, this is somewhat unsafe but should be safe enough 697 parser = new (_RT.getGlobalConfig("updateParser", myfaces._impl._util._HtmlStripper))(); 698 bodyData = parser.parse(newData, "body"); 699 700 var returnedElement = this.replaceHtmlItem(request, context, placeHolder, bodyData); 701 702 if (returnedElement) { 703 this._pushOperationResult(context, returnedElement); 704 } 705 return returnedElement; 706 }, 707 708 /** 709 * Replaces HTML elements through others and handle errors if the occur in the replacement part 710 * 711 * @param {Object} request (xhrRequest) 712 * @param {Object} context (Map) 713 * @param {Object} itemIdToReplace (String|Node) - ID of the element to replace 714 * @param {String} markup - the new tag 715 */ 716 replaceHtmlItem: function (request, context, itemIdToReplace, markup) { 717 var _Lang = this._Lang, _Dom = this._Dom; 718 719 var item = (!_Lang.isString(itemIdToReplace)) ? itemIdToReplace : 720 _Dom.byIdOrName(itemIdToReplace); 721 722 if (!item) { 723 throw this._raiseError(new Error(), _Lang.getMessage("ERR_ITEM_ID_NOTFOUND", null, "_AjaxResponse.replaceHtmlItem", (itemIdToReplace) ? itemIdToReplace.toString() : "undefined"), "replaceHtmlItem"); 724 } 725 return _Dom.outerHTML(item, markup, this._RT.getLocalOrGlobalConfig(context, "preserveFocus", false)); 726 }, 727 728 /** 729 * xml insert command handler 730 * 731 * @param request the ajax request element 732 * @param context the context element holding the data 733 * @param node the xml node holding the insert data 734 * @return true upon successful completion, false otherwise 735 * 736 **/ 737 processInsert: function (request, context, node) { 738 /*remapping global namespaces for speed and readability reasons*/ 739 var _Dom = this._Dom, 740 _Lang = this._Lang, 741 //determine which path to go: 742 insertData = this._parseInsertData(request, context, node); 743 744 if (!insertData) return false; 745 746 var opNode = _Dom.byIdOrName(insertData.opId); 747 if (!opNode) { 748 throw this._raiseError(new Error(), _Lang.getMessage("ERR_PPR_INSERTBEFID_1", null, "_AjaxResponse.processInsert", insertData.opId), "processInsert"); 749 } 750 751 //call insertBefore or insertAfter in our dom routines 752 var replacementFragment = _Dom[insertData.insertType](opNode, insertData.cDataBlock); 753 if (replacementFragment) { 754 this._pushOperationResult(context, replacementFragment); 755 } 756 return true; 757 }, 758 759 /** 760 * determines the corner data from the insert tag parsing process 761 * 762 * 763 * @param request request 764 * @param context context 765 * @param node the current node pointing to the insert tag 766 * @return false if the parsing failed, otherwise a map with follwing attributes 767 * <ul> 768 * <li>inserType - a ponter to a constant which maps the direct function name for the insert operation </li> 769 * <li>opId - the before or after id </li> 770 * <li>cDataBlock - the html cdata block which needs replacement </li> 771 * </ul> 772 * 773 * TODO we have to find a mechanism to replace the direct sendError calls with a javascript exception 774 * which we then can use for cleaner error code handling 775 */ 776 _parseInsertData: function (request, context, node) { 777 var _Lang = this._Lang, 778 _Dom = this._Dom, 779 concatCDATA = _Dom.concatCDATABlocks, 780 781 INSERT_TYPE_BEFORE = "insertBefore", 782 INSERT_TYPE_AFTER = "insertAfter", 783 784 id = node.getAttribute("id"), 785 beforeId = node.getAttribute("before"), 786 afterId = node.getAttribute("after"), 787 ret = {}; 788 789 //now we have to make a distinction between two different parsing paths 790 //due to a spec malalignment 791 //a <insert id="... beforeId|AfterId ="... 792 //b <insert><before id="..., <insert> <after id=".... 793 //see https://issues.apache.org/jira/browse/MYFACES-3318 794 //simple id, case1 795 if (id && beforeId && !afterId) { 796 ret.insertType = INSERT_TYPE_BEFORE; 797 ret.opId = beforeId; 798 ret.cDataBlock = concatCDATA(node); 799 800 //<insert id=".. afterId=".. 801 } else if (id && !beforeId && afterId) { 802 ret.insertType = INSERT_TYPE_AFTER; 803 ret.opId = afterId; 804 ret.cDataBlock = concatCDATA(node); 805 806 //<insert><before id="... <insert><after id="... 807 } else if (!id) { 808 var opType = node.childNodes[0].tagName; 809 810 if (opType != "before" && opType != "after") { 811 throw this._raiseError(new Error(), _Lang.getMessage("ERR_PPR_INSERTBEFID"), "_parseInsertData"); 812 } 813 opType = opType.toLowerCase(); 814 var beforeAfterId = node.childNodes[0].getAttribute("id"); 815 ret.insertType = (opType == "before") ? INSERT_TYPE_BEFORE : INSERT_TYPE_AFTER; 816 ret.opId = beforeAfterId; 817 ret.cDataBlock = concatCDATA(node.childNodes[0]); 818 } else { 819 throw this._raiseError(new Error(), [_Lang.getMessage("ERR_PPR_IDREQ"), 820 "\n ", 821 _Lang.getMessage("ERR_PPR_INSERTBEFID")].join(""), "_parseInsertData"); 822 } 823 ret.opId = _Lang.trim(ret.opId); 824 return ret; 825 }, 826 827 processDelete: function (request, context, node) { 828 829 var _Lang = this._Lang, 830 _Dom = this._Dom, 831 deleteId = node.getAttribute('id'); 832 833 if (!deleteId) { 834 throw this._raiseError(new Error(), _Lang.getMessage("ERR_PPR_UNKNOWNCID", null, "_AjaxResponse.processDelete", ""), "processDelete"); 835 } 836 837 var item = _Dom.byIdOrName(deleteId); 838 if (!item) { 839 throw this._raiseError(new Error(), _Lang.getMessage("ERR_PPR_UNKNOWNCID", null, "_AjaxResponse.processDelete", deleteId), "processDelete"); 840 } 841 842 var parentForm = this._Dom.getParent(item, "form"); 843 if (null != parentForm) { 844 context._mfInternal._updateForms.push(parentForm); 845 } 846 _Dom.deleteItem(item); 847 848 return true; 849 }, 850 851 processAttributes: function (request, context, node) { 852 //we now route into our attributes function to bypass 853 //IE quirks mode incompatibilities to the biggest possible extent 854 //most browsers just have to do a setAttributes but IE 855 //behaves as usual not like the official standard 856 //myfaces._impl._util.this._Dom.setAttribute(domNode, attribute, value; 857 858 var _Lang = this._Lang, 859 //<attributes id="id of element"> <attribute name="attribute name" value="attribute value" />* </attributes> 860 elemId = node.getAttribute('id'); 861 862 if (!elemId) { 863 throw this._raiseError(new Error(), "Error in attributes, id not in xml markup", "processAttributes"); 864 } 865 var childNodes = node.childNodes; 866 867 if (!childNodes) { 868 return false; 869 } 870 for (var loop2 = 0; loop2 < childNodes.length; loop2++) { 871 var attributesNode = childNodes[loop2], 872 attrName = attributesNode.getAttribute("name"), 873 attrValue = attributesNode.getAttribute("value"); 874 875 if (!attrName) { 876 continue; 877 } 878 879 attrName = _Lang.trim(attrName); 880 /*no value means reset*/ 881 //value can be of boolean value hence full check 882 if ('undefined' == typeof attrValue || null == attrValue) { 883 attrValue = ""; 884 } 885 886 switch (elemId) { 887 case this.P_VIEWROOT: 888 throw this._raiseError(new Error(), _Lang.getMessage("ERR_NO_VIEWROOTATTR", null, "_AjaxResponse.processAttributes"), "processAttributes"); 889 890 case this.P_VIEWHEAD: 891 throw this._raiseError(new Error(), _Lang.getMessage("ERR_NO_HEADATTR", null, "_AjaxResponse.processAttributes"), "processAttributes"); 892 893 case this.P_VIEWBODY: 894 var element = document.getElementsByTagName("body")[0]; 895 this._Dom.setAttribute(element, attrName, attrValue); 896 break; 897 898 default: 899 this._Dom.setAttribute(document.getElementById(elemId), attrName, attrValue); 900 break; 901 } 902 } 903 return true; 904 }, 905 906 /** 907 * internal helper which raises an error in the 908 * format we need for further processing 909 * 910 * @param message the message 911 * @param title the title of the error (optional) 912 * @param name the name of the error (optional) 913 */ 914 _raiseError: function (error, message, caller, title, name) { 915 var _Impl = this.attr("impl"); 916 var finalTitle = title || _Impl.MALFORMEDXML; 917 var finalName = name || _Impl.MALFORMEDXML; 918 var finalMessage = message || ""; 919 920 return this._Lang.makeException(error, finalTitle, finalName, this._nameSpace, caller || ( (arguments.caller) ? arguments.caller.toString() : "_raiseError"), finalMessage); 921 } 922 }); 923