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 _IFrameRequest
 20  * @memberOf myfaces._impl.xhrCore
 21  * @extends myfaces._impl.xhrCore._BaseRequest
 22  * @description
 23  * iframe transport for an alternative way to do ajax communication
 24  * <p />
 25  * The idea to make a frame a protocol transport is, to make a form submit
 26  * with the iframe as target, and once done use the result in the iframe
 27  * as result for the request.
 28  *
 29  * <p />
 30  * This method can be used by older browsers and if you have
 31  * a multipart request which includes
 32  * a fileupload element, fileuploads cannot be handled by
 33  * normal xhr requests. The standard html 4+ compliant way to do this
 34  * is to use an iframe as submit target for a form.
 35  * <p />
 36  * Note on almost all browsers this method induces a real asynchronity, the only
 37  * exception is firefox 3.6- which blocks the ui, this is resolved in Firefox 4
 38  */
 39 myfaces._impl.core._Runtime.extendClass("myfaces._impl.xhrCore._IFrameRequest", myfaces._impl.xhrCore._BaseRequest,
 40 /** @lends  myfaces._impl.xhrCore._IFrameRequest */
 41 {
 42 
 43     _FRAME_ID: "_mf_comm_frm",
 44     _frame: null,
 45     _RT: myfaces._impl.core._Runtime,
 46     CLS_NAME: "myfaces._impl.xhrCore._IFrameRequest",
 47 
 48     /**
 49      * @constant
 50      * @description request marker that the request is an iframe based request
 51      */
 52     JX_PART_IFRAME: "javax.faces.partial.iframe",
 53     /**
 54      * @constant
 55      * @description request marker that the request is an apache myfaces iframe request based request
 56      */
 57     MF_PART_IFRAME: "org.apache.myfaces.partial.iframe",
 58 
 59     /**
 60      * constructor which shifts the arguments
 61      * to the protected properties of this clas
 62      *
 63      * @param arguments
 64      */
 65     constructor_: function(arguments) {
 66         try {
 67             //we fetch in the standard arguments
 68             this._callSuper("constructor", arguments);
 69             this._Lang.applyArgs(this, arguments);
 70 
 71             if (!this._response) {
 72                 this._response = new myfaces._impl.xhrCore._AjaxResponse(this._onException, this._onWarning);
 73             }
 74             this._ajaxUtil = new myfaces._impl.xhrCore._AjaxUtils(this._onException, this._onWarning);
 75         } catch (e) {
 76             //_onError
 77             this._onException(null, this._context, this.CLS_NAME, "constructor", e);
 78         }
 79     },
 80 
 81     /**
 82      * send method, central callback which sends the
 83      * request
 84      */
 85     send: function() {
 86         var _Impl = this._getImpl();
 87         var _RT = myfaces._impl.core._Runtime;
 88 
 89         this._frame = this._createTransportFrame();
 90 
 91         //we append an onload handler to the frame
 92         //to cover the starting and loading events,
 93         //timeouts cannot be covered in a cross browser way
 94 
 95         //we point our onload handler to the frame, we do not use addOnLoad
 96         //because the frame should not have other onload handlers in place
 97         if (!_RT.browser.isIE) {
 98             this._frame.onload = this._Lang.hitch(this, this.callback);
 99         } else {
100             //ie has a bug, onload is not settable outside of innerHTML on iframes
101             this._frame.onload_IE = this._Lang.hitch(this, this.callback);
102         }
103        
104         //now to the parameter passing:
105         _Impl.sendEvent(this._xhr, this._context, _Impl.BEGIN);
106 
107         //viewstate should be in our parent form which we will isse we however have to add the execute and
108         //render parameters as well as the usual javax.faces.request params to our target
109 
110         var oldTarget = this._sourceForm.target;
111         var oldMethod = this._sourceForm.method;
112         var _progress = 0;
113         var _srcFrm = this._sourceForm;
114         try {
115             this._initAjaxParams();
116             _srcFrm.target = this._frame.name;
117             _srcFrm.method = this._ajaxType;
118             _srcFrm.submit();
119         } finally {
120             this._removeAjaxParams(oldTarget);
121             _srcFrm.target = oldTarget;
122             _srcFrm.method = oldMethod;
123         }
124     },
125 
126     /**
127      * the callback function after the request is done
128      */
129     callback: function() {
130         //now we have to do the processing, for that we have to parse the result, if it is a http 404 then
131         //nothing could be delivered and we bomb out with an error anything else has to be parsed
132         //via our xml parser
133         var request = {};
134         try {
135             request.responseText = this._getFrameText();
136             request.responseXML = this._getFrameXml();
137             request.readyState = this._READY_STATE_DONE;
138             this._xhr = request;
139             this._onDone(request, this._context);
140 
141             if (!this._Lang.isXMLParseError(request.responseXML)) {
142                 request.status = 201;
143                 this._onSuccess();
144             } else {
145                 request.status = 0;
146                 //we simulate the request for our xhr call
147                 this._onError();
148 
149             }
150         } catch (e) {
151             //_onError
152             this._onException(request, this._context, this.CLS_NAME, "constructor", e);
153         } finally {
154             //this closes any hanging or pending comm channel caused by the iframe
155             this._clearFrame();
156             this._frame = null;
157             this._xhr = null;
158         }
159     },
160 
161     /**
162      * returns the frame text in a browser independend manner
163      */
164     _getFrameDocument: function() {
165       //we cover various browsers here, because almost all browsers keep the document in a different
166       //position
167       return this._frame.contentWindow.document || this._frame.contentDocument || this._frame.document  ;
168     },
169 
170     _getFrameText: function() {
171         var framedoc = this._getFrameDocument();
172         //also ie keeps the body in framedoc.body the rest in documentElement
173         var body = framedoc.body || framedoc.documentElement ;
174         return  body.innerHTML;
175     },
176 
177     _clearFrame: function() {
178         var framedoc = this._getFrameDocument();
179         var body = framedoc.documentElement || framedoc.body;
180         //ie8 in 7 mode chokes on the innerHTML method
181         //direct dom removal is less flakey and works
182         //over all browsers, but is slower
183         this._Dom._removeChildNodes(body, false);
184     },
185 
186     /**
187      * returns the processed xml from the frame
188      */
189     _getFrameXml: function() {
190         var framedoc = this._getFrameDocument();
191         //same situation here, the xml is hosted either in xmlDocument or
192         //is located directly under the frame document
193         return  framedoc.XMLDocument ||  framedoc;
194     },
195 
196 
197     _initAjaxParams: function() {
198         var _Impl = this._getImpl();
199         //this._appendHiddenValue(_Impl.P_AJAX, "");
200         var appendHiddenValue = this._Lang.hitch(this, this._appendHiddenValue);
201         for (var key in this._passThrough) {
202 
203             appendHiddenValue(key, this._passThrough[key]);
204         }
205         //marker that this is an ajax iframe request
206         appendHiddenValue(this.JX_PART_IFRAME, "true");
207         appendHiddenValue(this.MF_PART_IFRAME, "true");
208 
209     },
210 
211 
212     _removeAjaxParams: function(oldTarget) {
213         var _Impl = this._getImpl();
214         this._sourceForm.target = oldTarget;
215         //some browsers optimize this and loose their scope that way,
216         //I am still not sure why, but probably because the function itself
217         //was called under finally and I ran into a bug in the fox 4
218         //scripting engine
219         var toDelete = [];
220         var possibleKeys = {};
221         for(var key in this._passThrough) {
222             possibleKeys[key] = true;
223         }
224         possibleKeys[this.JX_PART_IFRAME] = true;
225         possibleKeys[this.MF_PART_IFRAME] = true;
226         (possibleKeys["javax.faces.ViewState"])? delete possibleKeys["javax.faces.ViewState"]:null;
227 
228         for(var cnt = this._sourceForm.elements.length -1; cnt >= 0; cnt--) {
229             var elem = this._sourceForm.elements[cnt];
230             if(possibleKeys[elem.name] && elem.type == "hidden") {
231                 elem.parentNode.removeChild(elem);
232                 delete elem;
233             }
234         }
235     },
236 
237     _appendHiddenValue: function(key, value) {
238         if ('undefined' == typeof value) {
239             return;
240         }
241         var input = document.createElement("input");
242         //the dom is a singleton nothing can happen by remapping
243         this._Dom.setAttribute(input, "type", "hidden");
244         this._Dom.setAttribute(input, "name", key);
245         this._Dom.setAttribute(input, "style", "display:none");
246         this._Dom.setAttribute(input, "value", value);
247         this._sourceForm.appendChild(input);
248     },
249 
250     _removeHiddenValue: function(key) {
251         var elem = this._Dom.findByName(this._sourceForm, key, true);
252         if (elem.length) {
253 
254             elem[0].parentNode.removeChild(elem[0]);
255             delete elem[0];
256         }
257     },
258 
259     _createTransportFrame: function() {
260         var _RT = this._RT;
261         var frame = document.getElementById(this._FRAME_ID);
262         //normally this code should not be called
263         //but just to be sure
264         if (!frame) {
265             if (!_RT.browser.isIE) {
266                 frame = document.createElement('iframe');
267 
268                 //probably the ie method would work on all browsers
269                 //but this code is the safe bet it works on all standards
270                 //compliant browsers in a clean manner
271 
272                 this._Dom.setAttribute(frame, "src", "about:blank");
273                 this._Dom.setAttribute(frame, "id", this._FRAME_ID);
274                 this._Dom.setAttribute(frame, "name", this._FRAME_ID);
275                 this._Dom.setAttribute(frame, "type", "content");
276                 this._Dom.setAttribute(frame, "collapsed", "true");
277                 this._Dom.setAttribute(frame, "style", "display:none");
278 
279                 document.body.appendChild(frame);
280             } else { //Now to the non compliant browsers
281                 var node = document.createElement("div");
282                 this._Dom.setAttribute(node, "style", "display:none");
283                 //we are dealing with two well known iframe ie bugs here
284                 //first the iframe has to be set via innerHTML to be present
285                 //secondly the onload handler is immutable on ie, we have to
286                 //use a dummy onload handler in this case and call that one
287                 //from the onload handler
288                 node.innerHTML = "<iframe id='" + this._FRAME_ID + "' name='" + this._FRAME_ID + "' style='display:none;' src='about:blank' type='content' onload='this.onload_IE();'  ></iframe>";
289 
290                 //avoid the ie open tag problem
291                 var body = document.body;
292                 if (body.firstChild) {
293                     body.insertBefore(node, document.body.firstChild);
294                 } else {
295                     body.appendChild(node);
296                 }
297             }
298 
299         }
300         //helps to for the onload handlers and innerhtml to be in sync again
301         return document.getElementById(this._FRAME_ID);
302 
303     }
304 
305     //TODO pps, the idea behind pps is to generate another form
306     // and temporarily shift the elements over which have to be
307     // ppsed, but it is up for discussion if we do pps at all in case of
308     // an iframe, so I wont implement anything for now
309 });