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  * An implementation of an xhr request object
 19  * with partial page submit functionality, and faces
 20  * ppr request and timeout handling capabilities
 21  *
 22  * Author: Werner Punz (latest modification by $Author: ganeshpuri $)
 23  * Version: $Revision: 1.4 $ $Date: 2009/05/31 09:16:44 $
 24  */
 25 
 26 /**
 27  * @class
 28  * @name _AjaxRequest
 29  * @memberOf myfaces._impl.xhrCore
 30  * @extends myfaces._impl.core.Object
 31  */
 32 _MF_CLS(_PFX_XHR + "_AjaxRequest", _MF_OBJECT, /** @lends myfaces._impl.xhrCore._AjaxRequest.prototype */ {
 33 
 34     _contentType: "application/x-www-form-urlencoded",
 35     /** source element issuing the request */
 36     _source: null,
 37     /** context passed down from the caller */
 38     _context:null,
 39     /** source form issuing the request */
 40     _sourceForm: null,
 41     /** passthrough parameters */
 42     _passThrough: null,
 43 
 44     /** queue control */
 45     _timeout: null,
 46     /** enqueuing delay */
 47     //_delay:null,
 48     /** queue size */
 49     _queueSize:-1,
 50 
 51     /**
 52      back reference to the xhr queue,
 53      only set if the object really is queued
 54      */
 55     _xhrQueue: null,
 56 
 57     /** pps an array of identifiers which should be part of the submit, the form is ignored */
 58     _partialIdsArray : null,
 59 
 60     /** xhr object, internal param */
 61     _xhr: null,
 62 
 63     /** predefined method */
 64     _ajaxType:"POST",
 65 
 66     //CONSTANTS
 67     ENCODED_URL:"jakarta.faces.encodedURL",
 68     /*
 69      * constants used internally
 70      */
 71     _CONTENT_TYPE:"Content-Type",
 72     _HEAD_FACES_REQ:"Faces-Request",
 73     _VAL_AJAX: "partial/ajax",
 74     _XHR_CONST: myfaces._impl.xhrCore.engine.XhrConst,
 75 
 76     // _exception: null,
 77     // _requestParameters: null,
 78     /**
 79      * Constructor
 80      * <p />
 81      * note there is a load of common properties
 82      * inherited by the base class which define the corner
 83      * parameters and the general internal behavior
 84      * like _onError etc...
 85      * @param {Object} args an arguments map which an override any of the given protected
 86      * instance variables, by a simple name value pair combination
 87      */
 88     constructor_: function(args) {
 89 
 90         try {
 91             this._callSuper("constructor_", args);
 92 
 93             this._initDefaultFinalizableFields();
 94             delete this._resettableContent["_xhrQueue"];
 95 
 96             this.applyArgs(args);
 97 
 98             /*namespace remapping for readability*/
 99             //we fetch in the standard arguments
100             //and apply them to our protected attributes
101             //we do not gc the entry hence it is not defined on top
102             var xhrCore = myfaces._impl.xhrCore;
103             this._AJAXUTIL = xhrCore._AjaxUtils;
104 
105         } catch (e) {
106             //_onError
107             this._stdErrorHandler(this._xhr, this._context, e);
108         }
109     },
110 
111     /**
112      * Sends an Ajax request
113      */
114     send : function() {
115 
116         var _Lang = this._Lang;
117         var _RT = this._RT;
118 
119         try {
120 
121             var scopeThis = _Lang.hitch(this, function(functionName) {
122                 return _Lang.hitch(this, this[functionName]);
123             });
124             this._xhr = _Lang.mixMaps(this._getTransport(), {
125                 onprogress: scopeThis("onprogress"),
126                 ontimeout:  scopeThis("ontimeout"),
127 				//remove for xhr level2 support (chrome has problems with it)
128                 onloadend:  scopeThis("ondone"),
129                 onload:     scopeThis("onsuccess"),
130                 onerror:    scopeThis("onerror")
131 
132             }, true);
133             var xhr = this._xhr,
134                     sourceForm = this._sourceForm,
135                     targetURL = (typeof sourceForm.elements[this.ENCODED_URL] == 'undefined') ?
136                             sourceForm.action :
137                             sourceForm.elements[this.ENCODED_URL].value,
138                     formData = this.getFormData();
139 
140             for (var key in this._passThrough) {
141                 if(!this._passThrough.hasOwnProperty(key)) continue;
142                 formData.append(key, this._passThrough[key]);
143             }
144 
145             xhr.open(this._ajaxType, targetURL +
146                     ((this._ajaxType == "GET") ? "?" + this._formDataToURI(formData) : "")
147                     , true);
148 
149             xhr.timeout = this._timeout || 0;
150 
151             this._applyContentType(xhr);
152             xhr.setRequestHeader(this._HEAD_FACES_REQ, this._VAL_AJAX);
153 
154             //some webkit based mobile browsers do not follow the w3c spec of
155             // setting the accept headers automatically
156             if(this._RT.browser.isWebKit) {
157                 xhr.setRequestHeader("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
158             }
159             this._sendEvent("BEGIN");
160             //Check if it is a custom form data object
161             //if yes we use makefinal for the final handling
162             if (formData && formData.makeFinal) {
163                 formData = formData.makeFinal()
164             }
165             xhr.send((this._ajaxType != "GET") ? formData : null);
166 
167         } catch (e) {
168             //_onError//_onError
169             e = (e._mfInternal)? e: this._Lang.makeException(new Error(), "sendError","sendError", this._nameSpace, "send", e.message);
170             this._stdErrorHandler(this._xhr, this._context, e);
171         }
172     },
173 
174     /**
175      * helper, in multipart situations we might alter the content type
176      * from the urlencoded one
177      */
178     _applyContentType: function(xhr) {
179         var contentType = this._contentType+"; charset=utf-8";
180 
181         xhr.setRequestHeader(this._CONTENT_TYPE, contentType);
182     },
183 
184     ondone: function() {
185         this._requestDone();
186     },
187 
188 
189     onsuccess: function(/*evt*/) {
190 
191         var context = this._context;
192         var xhr = this._xhr;
193         try {
194             this._sendEvent("COMPLETE");
195             //now we have to reroute into our official api
196             //because users might want to decorate it, we will split it apart afterwards
197 
198             context._mfInternal = context._mfInternal || {};
199             faces.ajax.response((xhr.getXHRObject) ? xhr.getXHRObject() : xhr, context);
200 
201 
202 
203         } catch (e) {
204             this._stdErrorHandler(this._xhr, this._context, e);
205         }
206         //add for xhr level2 support
207         //}  finally {
208         //W3C spec onloadend must be called no matter if success or not
209         //    this.ondone();
210         //}
211     },
212 
213     onerror: function(/*evt*/) {
214         //TODO improve the error code detection here regarding server errors etc...
215         //and push it into our general error handling subframework
216         var context = this._context;
217         var xhr = this._xhr;
218         var _Lang = this._Lang;
219 
220         var errorText = "";
221         this._sendEvent("COMPLETE");
222         try {
223             var UNKNOWN = _Lang.getMessage("UNKNOWN");
224             //status can be 0 and statusText can be ""
225             var status = ('undefined' != xhr.status  && null != xhr.status)? xhr.status : UNKNOWN;
226             var statusText = ('undefined' != xhr.statusText  && null != xhr.statusText)? xhr.statusText : UNKNOWN;
227             errorText = _Lang.getMessage("ERR_REQU_FAILED", null,status,statusText);
228 
229         } catch (e) {
230             errorText = _Lang.getMessage("ERR_REQ_FAILED_UNKNOWN", null);
231         } finally {
232             var _Impl = this.attr("impl");
233                 _Impl.sendError(xhr, context, _Impl.HTTPERROR,
234                 _Impl.HTTPERROR, errorText,"","myfaces._impl.xhrCore._AjaxRequest","onerror");
235             //add for xhr level2 support
236             //since chrome does not call properly the onloadend we have to do it manually
237             //to eliminate xhr level1 for the compile profile modern
238             //W3C spec onloadend must be called no matter if success or not
239             //this.ondone();
240         }
241         //_onError
242     },
243 
244     onprogress: function(/*evt*/) {
245         //do nothing for now
246     },
247 
248     ontimeout: function(/*evt*/) {
249         try {
250             //we issue an event not an error here before killing the xhr process
251             this._sendEvent("TIMEOUT_EVENT");
252             //timeout done we process the next in the queue
253         } finally {
254             this._requestDone();
255         }
256     },
257 
258     _formDataToURI: function(formData) {
259         if (formData && formData.makeFinal) {
260             formData = formData.makeFinal()
261         }
262         return formData;
263     },
264 
265     /**
266      * change for faces 2.3 since we drop legacy browser support
267      * there is no need anymore to support xhr level 1.
268      * @returns {XMLHttpRequest} the transport object
269      * @private
270      */
271     _getTransport: function() {
272         return new XMLHttpRequest();
273     },
274 
275 
276     //----------------- backported from the base request --------------------------------
277     //non abstract ones
278 
279 
280     /**
281      * Spec. 13.3.1
282      * Collect and encode input elements.
283      * Additionally the hidden element jakarta.faces.ViewState
284      * Enhancement partial page submit
285      *
286      * @return  an element of formDataWrapper
287      * which keeps the final Send Representation of the
288      */
289     getFormData : function() {
290         var _AJAXUTIL = this._AJAXUTIL, myfacesOptions = this._context.myfaces, ret = null;
291 
292 
293 
294         if (!this._partialIdsArray || !this._partialIdsArray.length) {
295             var _AJAXUTIL = this._AJAXUTIL, myfacesOptions = this._context.myfaces;
296             return this._Lang.createFormDataDecorator(faces.getViewState(this._sourceForm));
297         } else {
298             //now this is less performant but we have to call it to allow viewstate decoration
299             ret = this._Lang.createFormDataDecorator(new Array());
300             _AJAXUTIL.encodeSubmittableFields(ret, this._sourceForm, this._partialIdsArray);
301             if (this._source && myfacesOptions && myfacesOptions.form)
302                 _AJAXUTIL.appendIssuingItem(this._source, ret);
303 
304         }
305         return ret;
306 
307     },
308 
309 
310 
311     /**
312      * Client error handlers which also in the long run route into our error queue
313      * but also are able to deliver more meaningful messages
314      * note, in case of an error all subsequent xhr requests are dropped
315      * to get a clean state on things
316      *
317      * @param request the xhr request object
318      * @param context the context holding all values for further processing
319      * @param exception the embedded exception
320      */
321     _stdErrorHandler: function(request, context, exception) {
322         var xhrQueue = this._xhrQueue;
323         try {
324              this.attr("impl").stdErrorHandler(request, context, exception);
325         } finally {
326             if (xhrQueue) {
327                 xhrQueue.cleanup();
328             }
329         }
330     },
331 
332     _sendEvent: function(evtType) {
333         var _Impl = this.attr("impl");
334         _Impl.sendEvent(this._xhr, this._context, _Impl[evtType]);
335     },
336 
337     _requestDone: function() {
338         var queue = this._xhrQueue;
339         if (queue) {
340             queue.processQueue();
341         }
342         //ie6 helper cleanup
343         delete this._context.source;
344 
345     }
346 });
347 
348