1/** 2* JsHttpRequest: JavaScript "AJAX" data loader 3* 4* @license LGPL 5* @author Dmitry Koterov, http://en.dklab.ru/lib/JsHttpRequest/ 6* @version 5.x $Id$ 7*/ 8 9// {{{ 10function JsHttpRequest() { 11 // Standard properties. 12 var t = this; 13 t.onreadystatechange = null; 14 t.readyState = 0; 15 t.responseText = null; 16 t.responseXML = null; 17 t.status = 200; 18 t.statusText = "OK"; 19 // JavaScript response array/hash 20 t.responseJS = null; 21 22 // Additional properties. 23 t.caching = false; // need to use caching? 24 t.loader = null; // loader to use ('form', 'script', 'xml'; null - autodetect) 25 t.session_name = "PHPSESSID"; // set to SID cookie or GET parameter name 26 27 // Internals. 28 t._ldObj = null; // used loader object 29 t._reqHeaders = []; // collected request headers 30 t._openArgs = null; // parameters from open() 31 t._errors = { 32 inv_form_el: 'Invalid FORM element detected: name=%, tag=%', 33 must_be_single_el: 'If used, <form> must be a single HTML element in the list.', 34 js_invalid: 'JavaScript code generated by backend is invalid!\n%', 35 url_too_long: 'Cannot use so long query with GET request (URL is larger than % bytes)', 36 unk_loader: 'Unknown loader: %', 37 no_loaders: 'No loaders registered at all, please check JsHttpRequest.LOADERS array', 38 no_loader_matched: 'Cannot find a loader which may process the request. Notices are:\n%' 39 } 40 41 /** 42 * Aborts the request. Behaviour of this function for onreadystatechange() 43 * is identical to IE (most universal and common case). E.g., readyState -> 4 44 * on abort() after send(). 45 */ 46 t.abort = function() { with (this) { 47 if (_ldObj && _ldObj.abort) _ldObj.abort(); 48 _cleanup(); 49 if (readyState == 0) { 50 // start->abort: no change of readyState (IE behaviour) 51 return; 52 } 53 if (readyState == 1 && !_ldObj) { 54 // open->abort: no onreadystatechange call, but change readyState to 0 (IE). 55 // send->abort: change state to 4 (_ldObj is not null when send() is called) 56 readyState = 0; 57 return; 58 } 59 _changeReadyState(4, true); // 4 in IE & FF on abort() call; Opera does not change to 4. 60 }} 61 62 /** 63 * Prepares the object for data loading. 64 * You may also pass URLs like "GET url" or "script.GET url". 65 */ 66 t.open = function(method, url, asyncFlag, username, password) { with (this) { 67 68 // Extract methor and loader from the URL (if present). 69 if (url.match(/^((\w+)\.)?(GET|POST)\s+(.*)/i)) { 70 this.loader = RegExp.$2? RegExp.$2 : null; 71 method = RegExp.$3; 72 url = RegExp.$4; 73 } 74 // Append SID to original URL. Use try...catch for security problems. 75 try { 76 if ( 77 document.location.search.match(new RegExp('[&?]' + session_name + '=([^&?]*)')) 78 || document.cookie.match(new RegExp('(?:;|^)\\s*' + session_name + '=([^;]*)')) 79 ) { 80 url += (url.indexOf('?') >= 0? '&' : '?') + session_name + "=" + this.escape(RegExp.$1); 81 } 82 } catch (e) {} 83 // Store open arguments to hash. 84 _openArgs = { 85 method: (method || '').toUpperCase(), 86 url: url, 87 asyncFlag: asyncFlag, 88 username: username != null? username : '', 89 password: password != null? password : '' 90 } 91 _ldObj = null; 92 _changeReadyState(1, true); // compatibility with XMLHttpRequest 93 return true; 94 }} 95 96 /** 97 * Sends a request to a server. 98 */ 99 t.send = function(content) { 100 if (!this.readyState) { 101 // send without open or after abort: no action (IE behaviour). 102 return; 103 } 104 this._changeReadyState(1, true); // compatibility with XMLHttpRequest 105 this._ldObj = null; 106 107 // Prepare to build QUERY_STRING from query hash. 108 var queryText = []; 109 var queryElem = []; 110 111 112 if (!this._hash2query(content, null, queryText, queryElem)) return; 113 114 // Solve the query hashcode & return on cache hit. 115 var hash = null; 116 if (this.caching && !queryElem.length) { 117 hash = this._openArgs.username + ':' + this._openArgs.password + '@' + this._openArgs.url + '|' + queryText + "#" + this._openArgs.method; 118 var cache = JsHttpRequest.CACHE[hash]; 119 if (cache) { 120 this._dataReady(cache[0], cache[1]); 121 return false; 122 } 123 } 124 125 // Try all the loaders. 126 var loader = (this.loader || '').toLowerCase(); 127 if (loader && !JsHttpRequest.LOADERS[loader]) return this._error('unk_loader', loader); 128 var errors = []; 129 var lds = JsHttpRequest.LOADERS; 130 for (var tryLoader in lds) { 131 var ldr = lds[tryLoader].loader; 132 if (!ldr) continue; // exclude possibly derived prototype properties from "for .. in". 133 if (loader && tryLoader != loader) continue; 134 // Create sending context. 135 var ldObj = new ldr(this); 136 JsHttpRequest.extend(ldObj, this._openArgs); 137 JsHttpRequest.extend(ldObj, { 138 queryText: queryText.join('&'), 139 queryElem: queryElem, 140 id: (new Date().getTime()) + "" + JsHttpRequest.COUNT++, 141 hash: hash, 142 span: null 143 }); 144 var error = ldObj.load(); 145 if (!error) { 146 // Save loading script. 147 this._ldObj = ldObj; 148 JsHttpRequest.PENDING[ldObj.id] = this; 149 return true; 150 } 151 if (!loader) { 152 errors[errors.length] = '- ' + tryLoader.toUpperCase() + ': ' + this._l(error); 153 } else { 154 return this._error(error); 155 } 156 } 157 158 // If no loader matched, generate error message. 159 return tryLoader? this._error('no_loader_matched', errors.join('\n')) : this._error('no_loaders'); 160 } 161 162 /** 163 * Returns all response headers (if supported). 164 */ 165 t.getAllResponseHeaders = function() { with (this) { 166 return _ldObj && _ldObj.getAllResponseHeaders? _ldObj.getAllResponseHeaders() : []; 167 }} 168 169 /** 170 * Returns one response header (if supported). 171 */ 172 t.getResponseHeader = function(label) { with (this) { 173 return _ldObj && _ldObj.getResponseHeader? _ldObj.getResponseHeader(label) : null; 174 }} 175 176 /** 177 * Adds a request header to a future query. 178 */ 179 t.setRequestHeader = function(label, value) { with (this) { 180 _reqHeaders[_reqHeaders.length] = [label, value]; 181 }}
181 182 183 // 184 // Internal functions. 185 // 186 187 /** 188 * Do all the work when a data is ready. 189 */ 190 t._dataReady = function(text, js) { with (this) { 191 if (caching && _ldObj) JsHttpRequest.CACHE[_ldObj.hash] = [text, js]; 192 responseText = responseXML = text; 193 responseJS = js; 194 if (js !== null) { 195 status = 200; 196 statusText = "OK"; 197 } else { 198 status = 500; 199 statusText = "Internal Server Error"; 200 } 201 _changeReadyState(2); 202 _changeReadyState(3); 203 _changeReadyState(4); 204 _cleanup(); 205 }} 206 207 /** 208 * Analog of sprintf(), but translates the first parameter by _errors. 209 */ 210 t._l = function(args) { 211 var i = 0, p = 0, msg = this._errors[args[0]]; 212 // Cannot use replace() with a callback, because it is incompatible with IE5. 213 while ((p = msg.indexOf('%', p)) >= 0) { 214 var a = args[++i] + ""; 215 msg = msg.substring(0, p) + a + msg.substring(p + 1, msg.length); 216 p += 1 + a.length; 217 } 218 return msg; 219 } 220 221 /** 222 * Called on error. 223 */ 224 t._error = function(msg) { 225 msg = this._l(typeof(msg) == 'string'? arguments : msg) 226 msg = "JsHttpRequest: " + msg; 227 if (!window.Error) { 228 // Very old browser... 229 throw msg; 230 } else if ((new Error(1, 'test')).description == "test") { 231 // We MUST (!!!) pass 2 parameters to the Error() constructor for IE5. 232 throw new Error(1, msg); 233 } else { 234 // Mozilla does not support two-parameter call style. 235 throw new Error(msg); 236 } 237 } 238 239 /** 240 * Convert hash to QUERY_STRING. 241 * If next value is scalar or hash, push it to queryText. 242 * If next value is form element, push [name, element] to queryElem. 243 */ 244 t._hash2query = function(content, prefix, queryText, queryElem) { 245 if (prefix == null) prefix = ""; 246 if((''+typeof(content)).toLowerCase() == 'object') { 247 var formAdded = false; 248 if (content && content.parentNode && content.parentNode.appendChild && content.tagName && content.tagName.toUpperCase() == 'FORM') { 249 content = { form: content }; 250 } 251 for (var k in content) { 252 var v = content[k]; 253 if (v instanceof Function) continue; 254 var curPrefix = prefix? prefix + '[' + this.escape(k) + ']' : this.escape(k); 255 var isFormElement = v && v.parentNode && v.parentNode.appendChild && v.tagName; 256 257 if (isFormElement) { 258 var tn = v.tagName.toUpperCase(); 259 260 if (tn == 'FORM') { 261 // FORM itself is passed. 262 formAdded = true; 263 } else if (tn == 'INPUT' || tn == 'TEXTAREA' || tn == 'SELECT') { 264 // This is a single form elemenent. 265 } else { 266 return this._error('inv_form_el', (v.name||''), v.tagName); 267 } 268 269 queryElem[queryElem.length] = { name: curPrefix, e: v }; 270 } else if (v instanceof Object) { 271 this._hash2query(v, curPrefix, queryText, queryElem); 272 } else { 273 // We MUST skip NULL values, because there is no method 274 // to pass NULL's via GET or POST request in PHP. 275 if (v === null) continue; 276 // Convert JS boolean true and false to corresponding PHP values. 277 if (v === true) v = 1; 278 if (v === false) v = ''; 279 queryText[queryText.length] = curPrefix + "=" + this.escape('' + v); 280 } 281 if (formAdded && queryElem.length > 1) { 282 return this._error('must_be_single_el'); 283 } 284 } 285 } else { 286 queryText[queryText.length] = content; 287 } 288 return true; 289 } 290 291 /** 292 * Remove last used script element (clean memory). 293 */ 294 t._cleanup = function() { 295 var ldObj = this._ldObj; 296 if (!ldObj) return; 297 // Mark this loading as aborted. 298 JsHttpRequest.PENDING[ldObj.id] = false; 299 var span = ldObj.span; 300 if (!span) return; 301 ldObj.span = null; 302 var closure = function() { 303 span.parentNode.removeChild(span); 304 } 305 // IE5 crashes on setTimeout(function() {...}, ...) construction! Use tmp variable. 306 JsHttpRequest.setTimeout(closure, 50); 307 } 308 309 /** 310 * Change current readyState and call trigger method. 311 */ 312 t._changeReadyState = function(s, reset) { with (this) { 313 if (reset) { 314 status = statusText = responseJS = null; 315 responseText = ''; 316 } 317 readyState = s; 318 if (onreadystatechange) onreadystatechange(); 319 }} 320 321 /** 322 * JS escape() does not quote '+'. 323 */ 324 t.escape = function(s) { 325 return escape(s).replace(new RegExp('\\+','g'), '%2B'); 326 } 327} 328 329 330// Global library variables. 331JsHttpRequest.COUNT = 0; // unique ID; used while loading IDs generation 332JsHttpRequest.MAX_URL_LEN = 2000; // maximum URL length 333JsHttpRequest.CACHE = {}; // cached data 334JsHttpRequest.PENDING = {}; // pending loadings 335JsHttpRequest.LOADERS = {}; // list of supported data loaders (filled at the bottom of the file) 336JsHttpRequest._dummy = function() {}; // avoid memory leaks 337 338 339/** 340* These functions are dirty hacks for IE 5.0 which does not increment a 341* reference counter for an object passed via setTimeout(). So, if this 342* object (closure function) is out of scope at the moment of timeout 343* applying, IE 5.0 crashes. 344*/ 345 346/** 347* Timeout wrappers storage. Used to avoid zeroing of referece counts in IE 5.0. 348* Please note that you MUST write "window.setTimeout", not "setTimeout", else 349* IE 5.0 crashes again. Strange, very strange... 350*/ 351JsHttpRequest.TIMEOUTS = { s: window.setTimeout, c: window.clearTimeout }; 352 353/** 354* Wrapper for IE5 buggy setTimeout. 355* Use this function instead of a usual setTimeout(). 356*/ 357JsHttpRequest.setTimeout = function(func, dt) { 358 // Always save inside the window object before a call (for FF)! 359 window.JsHttpRequest_tmp = JsHttpRequest.TIMEOUTS.s; 360 if (typeof(func) == "string") { 361 id = window.JsHttpRequest_tmp(func, dt); 362 } else { 363 var id = null; 364 var mediator = function() { 365 func(); 366 delete JsHttpRequest.TIMEOUTS[id]; // remove circular reference 367 } 368 id = window.JsHttpRequest_tmp(mediator, dt); 369 // Store a reference to the mediator function to the global array 370 // (reference count >= 1); use timeout ID as an array key; 371 JsHttpRequest.TIMEOUTS[id] = mediator; 372 } 373 window.JsHttpRequest_tmp = null; // no delete() in IE5 for window 374 return id; 375} 376 377/** 378* Complimental wrapper for clearTimeout. 379* Use this function instead of usual clearTimeout(). 380*/ 381JsHttpRequest.clearTimeout = function(id) { 382 window.JsHttpRequest_tmp = JsHttpRequest.TIMEOUTS.c; 383 delete JsHttpRequest.TIMEOUTS[id]; // remove circular reference 384 var r = window.JsHttpRequest_tmp(id); 385 window.JsHttpRequest_tmp = null; // no delete() in IE5 for window 386 return r; 387} 388 389 390/** 391* Global static function. 392* Simple interface for most popular use-cases. 393* You may also pass URLs like "GET url" or "script.GET url". 394*/ 395JsHttpRequest.query = function(url, content, onready, nocache) { 396 var req = new this(); 397 req.caching = !nocache; 398 req.onreadystatechange = function() { 399 if (req.readyState == 4) { 400 onready(req.responseJS, req.responseText); 401 } 402 } 403 req.open(null, url, true); 404 req.send(content); 405} 406 407 408/** 409* Global static function. 410* Called by server backend script on data load. 411*/ 412JsHttpRequest.dataReady = function(d) { 413 var th = this.PENDING[d.id]; 414 delete this.PENDING[d.id]; 415 if (th) { 416 th._dataReady(d.text, d.js); 417 } else if (th !== false) {
418 throw "dataReady(): unknown pending id: " + d.id; 419 } 420} 421 422 423// Adds all the properties of src to dest. 424JsHttpRequest.extend = function(dest, src) { 425 for (var k in src) dest[k] = src[k]; 426} 427 428/** 429* Each loader has the following properties which must be initialized: 430* - method 431* - url 432* - asyncFlag (ignored) 433* - username 434* - password 435* - queryText (string) 436* - queryElem (array) 437* - id 438* - hash 439* - span 440*/ 441 442// }}} 443 444// {{{ xml 445// Loader: XMLHttpRequest or ActiveX. 446// [+] GET and POST methods are supported. 447// [+] Most native and memory-cheap method. 448// [+] Backend data can be browser-cached. 449// [-] Cannot work in IE without ActiveX. 450// [-] No support for loading from different domains. 451// [-] No uploading support. 452// 453JsHttpRequest.LOADERS.xml = { loader: function(req) { 454 JsHttpRequest.extend(req._errors, { 455 xml_no: 'Cannot use XMLHttpRequest or ActiveX loader: not supported', 456 xml_no_diffdom: 'Cannot use XMLHttpRequest to load data from different domain %', 457 xml_no_headers: 'Cannot use XMLHttpRequest loader or ActiveX loader, POST method: headers setting is not supported, needed to work with encodings correctly', 458 xml_no_form_upl: 'Cannot use XMLHttpRequest loader: direct form elements using and uploading are not implemented' 459 }); 460 461 this.load = function() { 462 if (this.queryElem.length) return ['xml_no_form_upl']; 463 464 // XMLHttpRequest (and MS ActiveX'es) cannot work with different domains. 465 if (this.url.match(new RegExp('^([a-z]+://[^\\/]+)(.*)', 'i'))) { 466 // We MUST also check if protocols matched: cannot send from HTTP 467 // to HTTPS and vice versa. 468 if (RegExp.$1.toLowerCase() != document.location.protocol + '//' + document.location.hostname.toLowerCase()) { 469 return ['xml_no_diffdom', RegExp.$1]; 470 } 471 } 472 473 // Try to obtain a loader. 474 var xr = null; 475 if (window.XMLHttpRequest) { 476 try { xr = new XMLHttpRequest() } catch(e) {} 477 } else if (window.ActiveXObject) { 478 try { xr = new ActiveXObject("Microsoft.XMLHTTP") } catch(e) {} 479 if (!xr) try { xr = new ActiveXObject("Msxml2.XMLHTTP") } catch (e) {} 480 } 481 if (!xr) return ['xml_no']; 482 483 // Loading method detection. We cannot POST if we cannot set "octet-stream" 484 // header, because we need to process the encoded data in the backend manually. 485 var canSetHeaders = window.ActiveXObject || xr.setRequestHeader; 486 if (!this.method) this.method = canSetHeaders && this.queryText.length? 'POST' : 'GET'; 487 488 // Build & validate the full URL. 489 if (this.method == 'GET') { 490 if (this.queryText) this.url += (this.url.indexOf('?') >= 0? '&' : '?') + this.queryText; 491 this.queryText = ''; 492 if (this.url.length > JsHttpRequest.MAX_URL_LEN) return ['url_too_long', JsHttpRequest.MAX_URL_LEN]; 493 } else if (this.method == 'POST' && !canSetHeaders) { 494 return ['xml_no_headers']; 495 } 496 497 // Add ID to the url if we need to disable the cache. 498 this.url += (this.url.indexOf('?') >= 0? '&' : '?') + 'JsHttpRequest=' + (req.caching? '0' : this.id) + '-xml'; 499 500 // Assign the result handler. 501 var id = this.id; 502 xr.onreadystatechange = function() { 503 if (xr.readyState != 4) return; 504 // Avoid memory leak by removing the closure. 505 xr.onreadystatechange = JsHttpRequest._dummy; 506 req.status = null; 507 try { 508 // In case of abort() call, xr.status is unavailable and generates exception. 509 // But xr.readyState equals to 4 in this case. Stupid behaviour. :-( 510 req.status = xr.status; 511 req.responseText = xr.responseText; 512 } catch (e) {} 513 if (!req.status) return; 514 try { 515 // Prepare generator function & catch syntax errors on this stage. 516 eval('JsHttpRequest._tmp = function(id) { var d = ' + req.responseText + '; d.id = id; JsHttpRequest.dataReady(d); }'); 517 } catch (e) { 518 // Note that FF 2.0 does not throw any error from onreadystatechange handler. 519 return req._error('js_invalid', req.responseText) 520 } 521 // Call associated dataReady() outside the try-catch block 522 // to pass exceptions in onreadystatechange in usual manner. 523 JsHttpRequest._tmp(id); 524 JsHttpRequest._tmp = null; 525 }; 526 527 // Open & send the request. 528 xr.open(this.method, this.url, true, this.username, this.password); 529 if (canSetHeaders) { 530 // Pass pending headers. 531 for (var i = 0; i < req._reqHeaders.length; i++) { 532 xr.setRequestHeader(req._reqHeaders[i][0], req._reqHeaders[i][1]); 533 } 534 // Set non-default Content-type. We cannot use
535 // "application/x-www-form-urlencoded" here, because 536 // in PHP variable HTTP_RAW_POST_DATA is accessible only when 537 // enctype is not default (e.g., "application/octet-stream" 538 // is a good start). We parse POST data manually in backend 539 // library code. Note that Safari sets by default "x-www-form-urlencoded" 540 // header, but FF sets "text/xml" by default. 541 xr.setRequestHeader('Content-Type', 'application/octet-stream'); 542 } 543 xr.send(this.queryText); 544 545 // No SPAN is used for this loader. 546 this.span = null; 547 this.xr = xr; // save for later usage on abort() 548 549 // Success. 550 return null; 551 } 552 553 // Override req.getAllResponseHeaders method. 554 this.getAllResponseHeaders = function() { 555 return this.xr.getAllResponseHeaders(); 556 } 557 558 // Override req.getResponseHeader method. 559 this.getResponseHeader = function(label) { 560 return this.xr.getResponseHeader(label); 561 } 562 563 this.abort = function() { 564 this.xr.abort(); 565 this.xr = null; 566 } 567}} 568// }}} 569 570 571// {{{ script 572// Loader: SCRIPT tag. 573// [+] Most cross-browser.
574// [+] Supports loading from different domains. 575// [-] Only GET method is supported. 576// [-] No uploading support. 577// [-] Backend data cannot be browser-cached. 578// 579JsHttpRequest.LOADERS.script = { loader: function(req) { 580 JsHttpRequest.extend(req._errors, { 581 script_only_get: 'Cannot use SCRIPT loader: it supports only GET method', 582 script_no_form: 'Cannot use SCRIPT loader: direct form elements using and uploading are not implemented' 583 }) 584 585 this.load = function() { 586 // Move GET parameters to the URL itself. 587 if (this.queryText) this.url += (this.url.indexOf('?') >= 0? '&' : '?') + this.queryText; 588 this.url += (this.url.indexOf('?') >= 0? '&' : '?') + 'JsHttpRequest=' + this.id + '-' + 'script'; 589 this.queryText = ''; 590 591 if (!this.method) this.method = 'GET'; 592 if (this.method !== 'GET') return ['script_only_get']; 593 if (this.queryElem.length) return ['script_no_form']; 594 if (this.url.length > JsHttpRequest.MAX_URL_LEN) return ['url_too_long', JsHttpRequest.MAX_URL_LEN]; 595 596 var th = this, d = document, s = null, b = d.body; 597 if (!window.opera) { 598 // Safari, IE, FF, Opera 7.20. 599 this.span = s = d.createElement('SCRIPT'); 600 var closure = function() { 601 s.language = 'JavaScript'; 602 if (s.setAttribute) s.setAttribute('src', th.url); else s.src = th.url; 603 b.insertBefore(s, b.lastChild); 604 } 605 } else { 606 // Oh shit! Damned stupid Opera 7.23 does not allow to create SCRIPT 607 // element over createElement (in HEAD or BODY section or in nested SPAN - 608 // no matter): it is created deadly, and does not response the href assignment. 609 // So - always create SPAN. 610 this.span = s = d.createElement('SPAN'); 611 s.style.display = 'none'; 612 b.insertBefore(s, b.lastChild); 613 s.innerHTML = 'Workaround for IE.<s'+'cript></' + 'script>'; 614 var closure = function() { 615 s = s.getElementsByTagName('SCRIPT')[0]; // get with timeout! 616 s.language = 'JavaScript'; 617 if (s.setAttribute) s.setAttribute('src', th.url); else s.src = th.url; 618 } 619 } 620 JsHttpRequest.setTimeout(closure, 10); 621 622 // Success. 623 return null; 624 } 625}} 626// }}} 627 628 629// {{{ form 630// Loader: FORM & IFRAME. 631// [+] Supports file uploading. 632// [+] GET and POST methods are supported. 633// [+] Supports loading from different domains. 634// [-] Uses a lot of system resources. 635// [-] Backend data cannot be browser-cached. 636// [-] Pollutes browser history on some old browsers. 637// 638JsHttpRequest.LOADERS.form = { loader: function(req) { 639 JsHttpRequest.extend(req._errors, { 640 form_el_not_belong: 'Element "%" does not belong to any form!', 641 form_el_belong_diff: 'Element "%" belongs to a different form. All elements must belong to the same form!', 642 form_el_inv_enctype: 'Attribute "enctype" of the form must be "%" (for IE), "%" given.' 643 }) 644 645 this.load = function() { 646 var th = this; 647 648 if (!th.method) th.method = 'POST'; 649 th.url += (th.url.indexOf('?') >= 0? '&' : '?') + 'JsHttpRequest=' + th.id + '-' + 'form'; 650 651 // If GET, build full URL. Then copy QUERY_STRING to queryText. 652 if (th.method == 'GET') { 653 if (th.queryText) th.url += (th.url.indexOf('?') >= 0? '&' : '?') + th.queryText; 654 if (th.url.length > JsHttpRequest.MAX_URL_LEN) return ['url_too_long', JsHttpRequest.MAX_URL_LEN]; 655 var p = th.url.split('?', 2); 656 th.url = p[0]; 657 th.queryText = p[1] || ''; 658 } 659 660 // Check if all form elements belong to same form. 661 var form = null; 662 var wholeFormSending = false; 663 if (th.queryElem.length) { 664 if (th.queryElem[0].e.tagName.toUpperCase() == 'FORM') { 665 // Whole FORM sending. 666 form = th.queryElem[0].e; 667 wholeFormSending = true; 668 th.queryElem = []; 669 } else { 670 // If we have at least one form element, we use its FORM as a POST container. 671 form = th.queryElem[0].e.form; 672 // Validate all the elements. 673 for (var i = 0; i < th.queryElem.length; i++) { 674 var e = th.queryElem[i].e; 675 if (!e.form) { 676 return ['form_el_not_belong', e.name]; 677 } 678 if (e.form != form) { 679 return ['form_el_belong_diff', e.name]; 680 } 681 } 682 } 683 684 // Check enctype of the form. 685 if (th.method == 'POST') { 686 var need = "multipart/form-data"; 687 var given = (form.attributes.encType && form.attributes.encType.nodeValue) || (form.attributes.enctype && form.attributes.enctype.value) || form.enctype; 688 if (given != need) { 689 return ['form_el_inv_enctype', need, given]; 690 } 691 } 692 } 693 694 // Create invisible IFRAME with temporary form (form is used on empty queryElem). 695 // We ALWAYS create th IFRAME in the document of the form - for Opera 7.20. 696 var d = form && (form.ownerDocument || form.document) || document; 697 var ifname = 'jshr_i_' + th.id; 698 var s = th.span = d.createElement('DIV'); 699 s.style.position = 'absolute'; 700 s.style.display = 'none'; 701 s.style.visibility = 'hidden'; 702 s.innerHTML = 703 (form? '' : '<form' + (th.method == 'POST'? ' enctype="multipart/form-data" method="post"' : '') + '></form>') + // stupid IE, MUST use innerHTML assignment :-( 704 '<iframe name="' + ifname + '" id="' + ifname + '" style="width:0px; height:0px; overflow:hidden; border:none"></iframe>' 705 if (!form) { 706 form = th.span.firstChild; 707 } 708 709 // Insert generated form inside the document. 710 // Be careful: don't forget to close FORM container in document body! 711 d.body.insertBefore(s, d.body.lastChild); 712 713 // Function to safely set the form attributes. Parameter attr is NOT a hash 714 // but an array, because "for ... in" may badly iterate over derived attributes. 715 var setAttributes = function(e, attr) { 716 var sv = []; 717 var form = e; 718 // This strange algorythm is needed, because form may contain element 719 // with name like 'action'. In IE for such attribute will be returned 720 // form element node, not form action. Workaround: copy all attributes 721 // to new empty form and work with it, then copy them back. This is 722 // THE ONLY working algorythm since a lot of bugs in IE5.0 (e.g. 723 // with e.attributes property: causes IE crash). 724 if (e.mergeAttributes) { 725 var form = d.createElement('form'); 726 form.mergeAttributes(e, false); 727 } 728 for (var i = 0; i < attr.length; i++) { 729 var k = attr[i][0], v = attr[i][1]; 730 // TODO: http://forum.dklab.ru/viewtopic.php?p=129059#129059 731 sv[sv.length] = [k, form.getAttribute(k)]; 732 form.setAttribute(k, v); 733 } 734 if (e.mergeAttributes) { 735 e.mergeAttributes(form, false); 736 } 737 return sv; 738 } 739 740 // Run submit with delay - for old Opera: it needs some time to create IFRAME. 741 var closure = function() { 742 // Save JsHttpRequest object to new IFRAME. 743 top.JsHttpRequestGlobal = JsHttpRequest; 744 745 // Disable ALL the form elements. 746 var savedNames = []; 747 if (!wholeFormSending) { 748 for (var i = 0, n = form.elements.length; i < n; i++) { 749 savedNames[i] = form.elements[i].name; 750 form.elements[i].name = ''; 751 } 752 } 753 754 // Insert hidden fields to the form. 755 var qt = th.queryText.split('&'); 756 for (var i = qt.length - 1; i >= 0; i--) { 757 var pair = qt[i].split('=', 2); 758 var e = d.createElement('INPUT'); 759 e.type = 'hidden'; 760 e.name = unescape(pair[0]); 761 e.value = pair[1] != null? unescape(pair[1]) : ''; 762 form.appendChild(e); 763 } 764 765 766 // Change names of along user-passed form elements. 767 for (var i = 0; i < th.queryElem.length; i++) { 768 th.queryElem[i].e.name = th.queryElem[i].name; 769 } 770 771 // Temporary modify form attributes, submit form, restore attributes back. 772 var sv = setAttributes( 773 form, 774 [ 775 ['action', th.url], 776 ['method', th.method], 777 ['onsubmit', null], 778 ['target', ifname] 779 ] 780 ); 781 form.submit(); 782 setAttributes(form, sv); 783 784 // Remove generated temporary hidden elements from the top of the form. 785 for (var i = 0; i < qt.length; i++) { 786 // Use "form.firstChild.parentNode", not "form", or IE5 crashes! 787 form.lastChild.parentNode.removeChild(form.lastChild); 788 } 789 // Enable all disabled elements back. 790 if (!wholeFormSending) { 791 for (var i = 0, n = form.elements.length; i < n; i++) { 792 form.elements[i].name = savedNames[i]; 793 } 794 } 795 } 796 JsHttpRequest.setTimeout(closure, 100); 797 798 // Success. 799 return null; 800 } 801}} 802// }}} 803
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.