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