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:"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 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, "jakarta.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, "jakarta.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 jakarta.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