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 jsf
 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:"javax.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         var _Dom = this._Dom;
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                 //for chrome we have to emulate the onloadend by calling it explicitely
129                 //and leave the onload out
130                 //onloadend:  scopeThis("ondone"),
131                 onload:scopeThis("onsuccess"),
132                 onerror:scopeThis("onerror")
133 
134             }, true);
135 
136             this._applyClientWindowId();
137             var xhr = this._xhr,
138                     sourceForm = this._sourceForm,
139                     targetURL = (typeof sourceForm.elements[this.ENCODED_URL] == 'undefined') ?
140                             sourceForm.action :
141                             sourceForm.elements[this.ENCODED_URL].value,
142                     formData = this.getFormData();
143 
144             for (var key in this._passThrough) {
145                 if (!this._passThrough.hasOwnProperty(key)) continue;
146                 formData.append(key, this._passThrough[key]);
147             }
148 
149             xhr.open(this._ajaxType, targetURL +
150                     ((this._ajaxType == "GET") ? "?" + this._formDataToURI(formData) : "")
151                     , true);
152 
153             xhr.timeout = this._timeout || 0;
154 
155             this._applyContentType(xhr);
156             xhr.setRequestHeader(this._HEAD_FACES_REQ, this._VAL_AJAX);
157 
158             //some webkit based mobile browsers do not follow the w3c spec of
159             // setting the accept headers automatically
160             if (this._RT.browser.isWebKit) {
161                 xhr.setRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
162             }
163             this._sendEvent("BEGIN");
164             //Check if it is a custom form data object
165             //if yes we use makefinal for the final handling
166             if (formData && formData.makeFinal) {
167                 formData = formData.makeFinal()
168             }
169             xhr.send((this._ajaxType != "GET") ? formData : null);
170 
171         } catch (e) {
172             //_onError//_onError
173             e = (e._mfInternal) ? e : this._Lang.makeException(new Error(), "sendError", "sendError", this._nameSpace, "send", e.message);
174             this._stdErrorHandler(this._xhr, this._context, e);
175         } finally {
176             //no finally possible since the iframe uses real asynchronousity
177         }
178     },
179 
180     _applyClientWindowId:function () {
181         var clientWindow = this._Dom.getNamedElementFromForm(this._sourceForm, "javax.faces.ClientWindow");
182         //pass through if exists already set by _Impl
183         if ('undefined' != typeof this._context._mfInternal._clientWindow) {
184             this._context._mfInternal._clientWindowOld = clientWindow.value;
185             clientWindow.value = this._context._mfInternal._clientWindow;
186         } else {
187             if(clientWindow) {
188                 this._context._mfInternal._clientWindowDisabled = !! clientWindow.disabled;
189                 clientWindow.disabled = true;
190             }
191         }
192     },
193 
194     _restoreClientWindowId:function () {
195         //we have to reset the client window back to its original state
196 
197         var clientWindow = this._Dom.getNamedElementFromForm(this._sourceForm, "javax.faces.ClientWindow");
198         if(!clientWindow) {
199             return;
200         }
201         if ('undefined' != typeof this._context._mfInternal._clientWindowOld) {
202             clientWindow.value =  this._context._mfInternal._clientWindow;
203         }
204         if('undefined' != typeof this._context._mfInternal._clientWindowDisabled) {
205             //we reset it to the old value
206             clientWindow.disabled = this._context._mfInternal._clientWindowDisabled;
207         }
208     },
209 
210     /**
211      * applies the content type, this needs to be done only for xhr
212      * level1
213      * @param xhr
214      * @private
215      */
216     _applyContentType:function (xhr) {
217         var contentType = this._contentType + "; charset=utf-8";
218         xhr.setRequestHeader(this._CONTENT_TYPE, contentType);
219     },
220 
221     ondone:function () {
222         this._requestDone();
223     },
224 
225     onsuccess:function (/*evt*/) {
226         this._restoreClientWindowId();
227         var context = this._context;
228         var xhr = this._xhr;
229         try {
230             this._sendEvent("COMPLETE");
231             //now we have to reroute into our official api
232             //because users might want to decorate it, we will split it apart afterwards
233 
234             context._mfInternal = context._mfInternal || {};
235             jsf.ajax.response((xhr.getXHRObject) ? xhr.getXHRObject() : xhr, context);
236 
237         } catch (e) {
238             this._stdErrorHandler(this._xhr, this._context, e);
239 
240             //add for xhr level2 support
241         } finally {
242             //W3C spec onloadend must be called no matter if success or not
243             this.ondone();
244         }
245     },
246 
247     onerror:function (/*evt*/) {
248         this._restoreClientWindowId();
249         //TODO improve the error code detection here regarding server errors etc...
250         //and push it into our general error handling subframework
251         var context = this._context;
252         var xhr = this._xhr;
253         var _Lang = this._Lang;
254 
255         var errorText = "";
256         this._sendEvent("COMPLETE");
257         try {
258             var UNKNOWN = _Lang.getMessage("UNKNOWN");
259             //status can be 0 and statusText can be ""
260             var status = ('undefined' != xhr.status && null != xhr.status) ? xhr.status : UNKNOWN;
261             var statusText = ('undefined' != xhr.statusText && null != xhr.statusText) ? xhr.statusText : UNKNOWN;
262             errorText = _Lang.getMessage("ERR_REQU_FAILED", null, status, statusText);
263 
264         } catch (e) {
265             errorText = _Lang.getMessage("ERR_REQ_FAILED_UNKNOWN", null);
266         } finally {
267             try {
268                 var _Impl = this.attr("impl");
269                 _Impl.sendError(xhr, context, _Impl.HTTPERROR,
270                         _Impl.HTTPERROR, errorText, "", "myfaces._impl.xhrCore._AjaxRequest", "onerror");
271             } finally {
272                 //add for xhr level2 support
273                 //since chrome does not call properly the onloadend we have to do it manually
274                 //to eliminate xhr level1 for the compile profile modern
275                 //W3C spec onloadend must be called no matter if success or not
276                 this.ondone();
277             }
278         }
279         //_onError
280     },
281 
282     onprogress:function (/*evt*/) {
283         //do nothing for now
284     },
285 
286     ontimeout:function (/*evt*/) {
287         try {
288             this._restoreClientWindowId();
289             //we issue an event not an error here before killing the xhr process
290             this._sendEvent("TIMEOUT_EVENT");
291             //timeout done we process the next in the queue
292         } finally {
293             this._requestDone();
294         }
295     },
296 
297     _formDataToURI:function (formData) {
298         if (formData && formData.makeFinal) {
299             formData = formData.makeFinal()
300         }
301         return formData;
302     },
303 
304     _getTransport:function () {
305 
306         var xhr = this._RT.getXHRObject();
307         //the current xhr level2 timeout w3c spec is not implemented by the browsers yet
308         //we have to do a fallback to our custom routines
309 
310         //add for xhr level2 support
311         //Chrome fails in the current builds, on our loadend, we disable the xhr
312         //level2 optimisations for now
313         if (/*('undefined' == typeof this._timeout || null == this._timeout) &&*/ this._RT.getXHRLvl() >= 2) {
314             //no timeout we can skip the emulation layer
315             return xhr;
316         }
317         return new myfaces._impl.xhrCore.engine.Xhr1({xhrObject:xhr});
318     },
319 
320     //----------------- backported from the base request --------------------------------
321     //non abstract ones
322     /**
323      * Spec. 13.3.1
324      * Collect and encode input elements.
325      * Additionally the hidden element javax.faces.ViewState
326      *
327      *
328      * @return  an element of formDataWrapper
329      * which keeps the final Send Representation of the
330      */
331     getFormData:function () {
332         var _AJAXUTIL = this._AJAXUTIL, myfacesOptions = this._context.myfaces;
333         return this._Lang.createFormDataDecorator(jsf.getViewState(this._sourceForm));
334     },
335 
336     /**
337      * Client error handlers which also in the long run route into our error queue
338      * but also are able to deliver more meaningful messages
339      * note, in case of an error all subsequent xhr requests are dropped
340      * to get a clean state on things
341      *
342      * @param request the xhr request object
343      * @param context the context holding all values for further processing
344      * @param exception the embedded exception
345      */
346     _stdErrorHandler:function (request, context, exception) {
347         var xhrQueue = this._xhrQueue;
348         try {
349             this.attr("impl").stdErrorHandler(request, context, exception);
350         } finally {
351             if (xhrQueue) {
352                 xhrQueue.cleanup();
353             }
354         }
355     },
356 
357     _sendEvent:function (evtType) {
358         var _Impl = this.attr("impl");
359         _Impl.sendEvent(this._xhr, this._context, _Impl[evtType]);
360     },
361 
362     _requestDone:function () {
363         var queue = this._xhrQueue;
364         if (queue) {
365             queue.processQueue();
366         }
367         //ie6 helper cleanup
368         delete this._context.source;
369         this._finalize();
370     },
371 
372     //cleanup
373     _finalize:function () {
374         if (this._xhr.readyState == this._XHR_CONST.READY_STATE_DONE) {
375             this._callSuper("_finalize");
376         }
377     }
378 });
379 
380