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