2 * JsHttpRequest: JavaScript "AJAX" data loader
5 * @author Dmitry Koterov, http://en.dklab.ru/lib/JsHttpRequest/
10 function JsHttpRequest() {
11 // Standard properties.
13 t.onreadystatechange = null;
15 t.responseText = null;
19 // JavaScript response array/hash
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
28 t._ldObj = null; // used loader object
29 t._reqHeaders = []; // collected request headers
30 t._openArgs = null; // parameters from open()
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%'
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().
46 t.abort = function() { with (this) {
47 if (_ldObj && _ldObj.abort) _ldObj.abort();
49 if (readyState == 0) {
50 // start->abort: no change of readyState (IE behaviour)
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)
59 _changeReadyState(4, true); // 4 in IE & FF on abort() call; Opera does not change to 4.
63 * Prepares the object for data loading.
64 * You may also pass URLs like "GET url" or "script.GET url".
66 t.open = function(method, url, asyncFlag, username, password) { with (this) {
67 // Extract methor and loader from the URL (if present).
68 if (url.match(/^((\w+)\.)?(GET|POST)\s+(.*)/i)) {
69 this.loader = RegExp.$2? RegExp.$2 : null;
73 // Append SID to original URL. Use try...catch for security problems.
76 document.location.search.match(new RegExp('[&?]' + session_name + '=([^&?]*)'))
77 || document.cookie.match(new RegExp('(?:;|^)\\s*' + session_name + '=([^;]*)'))
79 url += (url.indexOf('?') >= 0? '&' : '?') + session_name + "=" + this.escape(RegExp.$1);
82 // Store open arguments to hash.
84 method: (method || '').toUpperCase(),
87 username: username != null? username : '',
88 password: password != null? password : ''
91 _changeReadyState(1, true); // compatibility with XMLHttpRequest
96 * Sends a request to a server.
98 t.send = function(content) {
99 if (!this.readyState) {
100 // send without open or after abort: no action (IE behaviour).
103 this._changeReadyState(1, true); // compatibility with XMLHttpRequest
106 // Prepare to build QUERY_STRING from query hash.
109 if (!this._hash2query(content, null, queryText, queryElem)) return;
111 // Solve the query hashcode & return on cache hit.
113 if (this.caching && !queryElem.length) {
114 hash = this._openArgs.username + ':' + this._openArgs.password + '@' + this._openArgs.url + '|' + queryText + "#" + this._openArgs.method;
115 var cache = JsHttpRequest.CACHE[hash];
117 this._dataReady(cache[0], cache[1]);
122 // Try all the loaders.
123 var loader = (this.loader || '').toLowerCase();
124 if (loader && !JsHttpRequest.LOADERS[loader]) return this._error('unk_loader', loader);
126 var lds = JsHttpRequest.LOADERS;
127 for (var tryLoader in lds) {
128 var ldr = lds[tryLoader].loader;
129 if (!ldr) continue; // exclude possibly derived prototype properties from "for .. in".
130 if (loader && tryLoader != loader) continue;
131 // Create sending context.
132 var ldObj = new ldr(this);
133 JsHttpRequest.extend(ldObj, this._openArgs);
134 JsHttpRequest.extend(ldObj, {
135 queryText: queryText.join('&'),
136 queryElem: queryElem,
137 id: (new Date().getTime()) + "" + JsHttpRequest.COUNT++,
141 var error = ldObj.load();
143 // Save loading script.
145 JsHttpRequest.PENDING[ldObj.id] = this;
149 errors[errors.length] = '- ' + tryLoader.toUpperCase() + ': ' + this._l(error);
151 return this._error(error);
155 // If no loader matched, generate error message.
156 return tryLoader? this._error('no_loader_matched', errors.join('\n')) : this._error('no_loaders');
160 * Returns all response headers (if supported).
162 t.getAllResponseHeaders = function() { with (this) {
163 return _ldObj && _ldObj.getAllResponseHeaders? _ldObj.getAllResponseHeaders() : [];
167 * Returns one response header (if supported).
169 t.getResponseHeader = function(label) { with (this) {
170 return _ldObj && _ldObj.getResponseHeader? _ldObj.getResponseHeader(label) : null;
174 * Adds a request header to a future query.
176 t.setRequestHeader = function(label, value) { with (this) {
177 _reqHeaders[_reqHeaders.length] = [label, value];
181 // Internal functions.
185 * Do all the work when a data is ready.
187 t._dataReady = function(text, js) { with (this) {
188 if (caching && _ldObj) JsHttpRequest.CACHE[_ldObj.hash] = [text, js];
189 responseText = responseXML = text;
195 // The special value "null" from a backend means Fatal error.
196 // User cannot assign null to $_RESULT manually, it is
197 // translated to false to avoid 500 error collisions.
199 statusText = "Internal Server Error";
201 _changeReadyState(2);
202 _changeReadyState(3);
203 _changeReadyState(4);
208 * Analog of sprintf(), but translates the first parameter by _errors.
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);
224 t._error = function(msg) {
225 msg = this._l(typeof(msg) == 'string'? arguments : msg)
226 msg = "JsHttpRequest: " + msg;
228 // Very old browser...
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);
234 // Mozilla does not support two-parameter call style.
235 throw new Error(msg);
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.
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 };
251 for (var k in content) {
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;
257 var tn = v.tagName.toUpperCase();
259 // FORM itself is passed.
261 } else if (tn == 'INPUT' || tn == 'TEXTAREA' || tn == 'SELECT' || tn == 'BUTTON') {
262 // This is a single form elemenent.
264 return this._error('inv_form_el', (v.name||''), v.tagName);
266 queryElem[queryElem.length] = { name: curPrefix, e: v };
267 } else if (v instanceof Object) {
268 this._hash2query(v, curPrefix, queryText, queryElem);
270 // We MUST skip NULL values, because there is no method
271 // to pass NULL's via GET or POST request in PHP.
272 if (v === null) continue;
273 // Convert JS boolean true and false to corresponding PHP values.
274 if (v === true) v = 1;
275 if (v === false) v = '';
276 queryText[queryText.length] = curPrefix + "=" + this.escape('' + v);
278 if (formAdded && queryElem.length > 1) {
279 return this._error('must_be_single_el');
283 queryText[queryText.length] = content;
289 * Remove last used script element (clean memory).
291 t._cleanup = function() {
292 var ldObj = this._ldObj;
294 // Mark this loading as aborted.
295 JsHttpRequest.PENDING[ldObj.id] = false;
296 var span = ldObj.span;
298 // Do NOT use iframe.contentWindow.back() - it is incompatible with Opera 9!
300 var closure = function() {
301 span.parentNode.removeChild(span);
303 // IE5 crashes on setTimeout(function() {...}, ...) construction! Use tmp variable.
304 JsHttpRequest.setTimeout(closure, 50);
308 * Change current readyState and call trigger method.
310 t._changeReadyState = function(s, reset) { with (this) {
312 status = statusText = responseJS = null;
316 if (onreadystatechange) onreadystatechange();
320 * JS escape() does not quote '+'.
322 t.escape = function(s) {
323 return escape(s).replace(new RegExp('\\+','g'), '%2B');
328 // Global library variables.
329 JsHttpRequest.COUNT = 0; // unique ID; used while loading IDs generation
330 JsHttpRequest.MAX_URL_LEN = 2000; // maximum URL length
331 JsHttpRequest.CACHE = {}; // cached data
332 JsHttpRequest.PENDING = {}; // pending loadings
333 JsHttpRequest.LOADERS = {}; // list of supported data loaders (filled at the bottom of the file)
334 JsHttpRequest._dummy = function() {}; // avoid memory leaks
338 * These functions are dirty hacks for IE 5.0 which does not increment a
339 * reference counter for an object passed via setTimeout(). So, if this
340 * object (closure function) is out of scope at the moment of timeout
341 * applying, IE 5.0 crashes.
345 * Timeout wrappers storage. Used to avoid zeroing of referece counts in IE 5.0.
346 * Please note that you MUST write "window.setTimeout", not "setTimeout", else
347 * IE 5.0 crashes again. Strange, very strange...
349 JsHttpRequest.TIMEOUTS = { s: window.setTimeout, c: window.clearTimeout };
352 * Wrapper for IE5 buggy setTimeout.
353 * Use this function instead of a usual setTimeout().
355 JsHttpRequest.setTimeout = function(func, dt) {
356 // Always save inside the window object before a call (for FF)!
357 window.JsHttpRequest_tmp = JsHttpRequest.TIMEOUTS.s;
358 if (typeof(func) == "string") {
359 id = window.JsHttpRequest_tmp(func, dt);
362 var mediator = function() {
364 delete JsHttpRequest.TIMEOUTS[id]; // remove circular reference
366 id = window.JsHttpRequest_tmp(mediator, dt);
367 // Store a reference to the mediator function to the global array
368 // (reference count >= 1); use timeout ID as an array key;
369 JsHttpRequest.TIMEOUTS[id] = mediator;
371 window.JsHttpRequest_tmp = null; // no delete() in IE5 for window
376 * Complimental wrapper for clearTimeout.
377 * Use this function instead of usual clearTimeout().
379 JsHttpRequest.clearTimeout = function(id) {
380 window.JsHttpRequest_tmp = JsHttpRequest.TIMEOUTS.c;
381 delete JsHttpRequest.TIMEOUTS[id]; // remove circular reference
382 var r = window.JsHttpRequest_tmp(id);
383 window.JsHttpRequest_tmp = null; // no delete() in IE5 for window
389 * Global static function.
390 * Simple interface for most popular use-cases.
391 * You may also pass URLs like "GET url" or "script.GET url".
393 JsHttpRequest.query = function(url, content, onready, nocache) {
394 var req = new this();
395 req.caching = !nocache;
396 req.onreadystatechange = function() {
397 if (req.readyState == 4) {
398 onready(req.responseJS, req.responseText);
401 req.open(null, url, true);
407 * Global static function.
408 * Called by server backend script on data load.
410 JsHttpRequest.dataReady = function(d) {
411 var th = this.PENDING[d.id];
412 delete this.PENDING[d.id];
414 th._dataReady(d.text, d.js);
415 } else if (th !== false) {
416 throw "dataReady(): unknown pending id: " + d.id;
421 // Adds all the properties of src to dest.
422 JsHttpRequest.extend = function(dest, src) {
423 for (var k in src) dest[k] = src[k];
427 * Each loader has the following properties which must be initialized:
430 * - asyncFlag (ignored)
433 * - queryText (string)
434 * - queryElem (array)
443 // Loader: XMLHttpRequest or ActiveX.
444 // [+] GET and POST methods are supported.
445 // [+] Most native and memory-cheap method.
446 // [+] Backend data can be browser-cached.
447 // [-] Cannot work in IE without ActiveX.
448 // [-] No support for loading from different domains.
449 // [-] No uploading support.
451 JsHttpRequest.LOADERS.xml = { loader: function(req) {
452 JsHttpRequest.extend(req._errors, {
453 xml_no: 'Cannot use XMLHttpRequest or ActiveX loader: not supported',
454 xml_no_diffdom: 'Cannot use XMLHttpRequest to load data from different domain %',
455 xml_no_headers: 'Cannot use XMLHttpRequest loader or ActiveX loader, POST method: headers setting is not supported, needed to work with encodings correctly',
456 xml_no_form_upl: 'Cannot use XMLHttpRequest loader: direct form elements using and uploading are not implemented'
459 this.load = function() {
460 if (this.queryElem.length) return ['xml_no_form_upl'];
462 // XMLHttpRequest (and MS ActiveX'es) cannot work with different domains.
463 if (this.url.match(new RegExp('^([a-z]+://[^\\/]+)(.*)((:[0-9]*)+)', 'i'))) {
464 // We MUST also check if protocols matched: cannot send from HTTP
465 // to HTTPS and vice versa.
466 if (RegExp.$1.toLowerCase() != document.location.protocol + '//' + document.location.hostname.toLowerCase()) {
467 return ['xml_no_diffdom', RegExp.$1];
471 // Try to obtain a loader.
473 if (window.XMLHttpRequest) {
474 try { xr = new XMLHttpRequest() } catch(e) {}
475 } else if (window.ActiveXObject) {
476 try { xr = new ActiveXObject("Microsoft.XMLHTTP") } catch(e) {}
477 if (!xr) try { xr = new ActiveXObject("Msxml2.XMLHTTP") } catch (e) {}
479 if (!xr) return ['xml_no'];
481 // Loading method detection. We cannot POST if we cannot set "octet-stream"
482 // header, because we need to process the encoded data in the backend manually.
483 var canSetHeaders = window.ActiveXObject || xr.setRequestHeader;
484 if (!this.method) this.method = canSetHeaders && this.queryText.length? 'POST' : 'GET';
486 // Build & validate the full URL.
487 if (this.method == 'GET') {
488 if (this.queryText) this.url += (this.url.indexOf('?') >= 0? '&' : '?') + this.queryText;
490 if (this.url.length > JsHttpRequest.MAX_URL_LEN) return ['url_too_long', JsHttpRequest.MAX_URL_LEN];
491 } else if (this.method == 'POST' && !canSetHeaders) {
492 return ['xml_no_headers'];
495 // Add ID to the url if we need to disable the cache.
496 this.url += (this.url.indexOf('?') >= 0? '&' : '?') + 'JsHttpRequest=' + (req.caching? '0' : this.id) + '-xml';
498 // Assign the result handler.
500 xr.onreadystatechange = function() {
501 if (xr.readyState != 4) return;
502 // Avoid memory leak by removing the closure.
503 xr.onreadystatechange = JsHttpRequest._dummy;
506 // In case of abort() call, xr.status is unavailable and generates exception.
507 // But xr.readyState equals to 4 in this case. Stupid behaviour. :-(
508 req.status = xr.status;
509 req.responseText = xr.responseText;
511 if (!req.status) return;
513 // Damned Opera returned empty responseText when Status is not 200.
514 var rtext = req.responseText || '{ js: null, text: null }';
515 // Prepare generator function & catch syntax errors on this stage.
516 eval('JsHttpRequest._tmp = function(id) { var d = ' + rtext + '; d.id = id; JsHttpRequest.dataReady(d); }');
518 // Note that FF 2.0 does not throw any error from onreadystatechange handler.
519 return req._error('js_invalid', req.responseText)
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;
527 // Open & send the request.
528 xr.open(this.method, this.url, true, this.username, this.password);
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]);
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');
543 xr.send(this.queryText);
545 // No SPAN is used for this loader.
547 this.xr = xr; // save for later usage on abort()
553 // Override req.getAllResponseHeaders method.
554 this.getAllResponseHeaders = function() {
555 return this.xr.getAllResponseHeaders();
558 // Override req.getResponseHeader method.
559 this.getResponseHeader = function(label) {
560 return this.xr.getResponseHeader(label);
563 this.abort = function() {
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.
579 JsHttpRequest.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'
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';
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];
596 var th = this, d = document, s = null, b = d.body;
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);
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;
620 JsHttpRequest.setTimeout(closure, 10);
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.
638 JsHttpRequest.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.'
645 this.load = function() {
648 if (!th.method) th.method = 'POST';
649 th.url += (th.url.indexOf('?') >= 0? '&' : '?') + 'JsHttpRequest=' + th.id + '-' + 'form';
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);
657 th.queryText = p[1] || '';
660 // Check if all form elements belong to same form.
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;
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;
676 return ['form_el_not_belong', e.name];
678 if (e.form != form) {
679 return ['form_el_belong_diff', e.name];
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;
689 return ['form_el_inv_enctype', need, given];
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';
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>'
706 form = th.span.firstChild;
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);
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) {
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);
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);
734 if (e.mergeAttributes) {
735 e.mergeAttributes(form, false);
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;
745 // Disable ALL the form elements.
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 = '';
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');
760 e.name = unescape(pair[0]);
761 e.value = pair[1] != null? unescape(pair[1]) : '';
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;
771 // Temporary modify form attributes, submit form, restore attributes back.
772 var sv = setAttributes(
776 ['method', th.method],
782 setAttributes(form, sv);
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);
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];
796 JsHttpRequest.setTimeout(closure, 100);