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