6c5c8e348ec3def39c6e5fb06293972d19514d65
[webpac2] / web / iwf / iwfajax.js
1 // -----------------------------------------------------------------------------\r
2 // IWF - Interactive Website Framework.  Javascript library for creating\r
3 // responsive thin client interfaces.\r
4 //\r
5 // Copyright (C) 2005 Brock Weaver brockweaver@gmail.com\r
6 //\r
7 //     This library is free software; you can redistribute it and/or modify\r
8 // it under the terms of the GNU Lesser General Public License as published\r
9 // by the Free Software Foundation; either version 2.1 of the License, or\r
10 // (at your option) any later version.\r
11 //\r
12 //     This library is distributed in the hope that it will be useful, but\r
13 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\r
14 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public\r
15 // License for more details.\r
16 //\r
17 //    You should have received a copy of the GNU Lesser General Public License\r
18 // along with this library; if not, write to the Free Software Foundation,\r
19 // Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\r
20 //\r
21 // Brock Weaver\r
22 // brockweaver@gmail.com\r
23 // 1605 NW Maple Pl\r
24 // Ankeny, IA 50021\r
25 // -----------------------------------------------------------------------------\r
26 \r
27 // --------------------------------------------------------------------------\r
28 // iwfajax.js\r
29 //\r
30 // Thread-safe background xml request via XmlHttpRequest object\r
31 //\r
32 // Dependencies:\r
33 // iwfxml.js\r
34 // iwfcore.js\r
35 // iwfgui.js (optional -- pretty progress bar)\r
36 //\r
37 // Brock Weaver - brockweaver@sourceforge.net - iwf.sourceforge.net\r
38 // v 0.1 - 2005-06-05\r
39 // Initial release.\r
40 // --------------------------------------------------------------------------\r
41 // Known Issues:\r
42 //  AddToHistory does not work\r
43 //  Iframe not implemented\r
44 // --------------------------------------------------------------------------\r
45 \r
46 if (!window.iwfGetById || !window.iwfXmlDoc){\r
47         iwfLog("IWF Dependency Error: iwfajax.js is dependent upon both iwfcore.js and iwfxml.js, so you *must* reference those files first.\n\nExample:\n\n<script type='text/javascript' src='iwfcore.js'></script>\n<script type='text/javascript' src='iwfxml.js'></script>\n<script type='text/javascript' src='iwfajax.js'></script>", true);\r
48 }\r
49 \r
50 // -----------------------------------\r
51 // Begin: AJAX Request and Response\r
52 // -----------------------------------\r
53 \r
54 function iwfRequest(urlOrForm, targetElementOnResponse, addToHistory, callback){\r
55 \r
56         // we use a javascript feature here called "inner functions"\r
57         // using these means the local variables retain their values after the outer function\r
58         // has returned.  this is useful for thread safety, so\r
59         // reassigning the onreadystatechange function doesn't stomp over earlier requests.\r
60 \r
61         function iwfBindCallback(){\r
62                 if (req.readyState == 4) {\r
63                         _iwfOnRequestEnd();\r
64                         if (req.status == 200 || req.status == 0) {\r
65 iwfLog('exact response from server:\n\n' + req.responseText);\r
66                                 _iwfResponseHandler(req.responseText, localCallback, localTarget);\r
67                         } else {\r
68                                 _iwfOnRequestError(req.status, req.statusText, req.responseText);\r
69                         }\r
70                         return;\r
71                 }\r
72         }\r
73 \r
74         // determine how to hit the server...\r
75         var url = null;\r
76         var method = 'GET';\r
77         var postdata = null;\r
78         var isFromForm = true;\r
79         var contentType = 'application/x-www-form-urlencoded';\r
80 \r
81         if (iwfIsString(urlOrForm)){\r
82 \r
83                 // if we get here, they either specified the url or the name of the form.\r
84                 // either way, flag so we return nothing.\r
85                 isFromForm = false;\r
86 \r
87                 var frm = iwfGetForm(urlOrForm);\r
88                 if (!frm){\r
89                         // is a url.\r
90                         url = urlOrForm;\r
91                         method = 'GET';\r
92                         postdata = null;\r
93                 } else {\r
94                         // is name of a form.\r
95                         // fill with the form object.\r
96                         urlOrForm = frm;\r
97                 }\r
98 \r
99         }\r
100 \r
101         // use a local variable to hold our callback and target until the inner function is called...\r
102         var localCallback = callback;\r
103         var localTarget = targetElementOnResponse;\r
104 \r
105         if (!iwfIsString(urlOrForm)){\r
106 \r
107                 var ctl = null;\r
108 \r
109 \r
110                 // is a form or a control in the form.\r
111                 if (iwfExists(urlOrForm.form)){\r
112                         // is a control in the form. jump up to the form.\r
113                         ctl = urlOrForm;\r
114                         urlOrForm = urlOrForm.form;\r
115                 }\r
116 \r
117 \r
118                 // if they passed a form and no local target, lookup the form.iwfTarget attribute and use it if possible\r
119                 if (!localTarget){\r
120                                 localTarget = iwfAttribute(urlOrForm, 'iwfTarget');\r
121                 }\r
122 \r
123                 if (localTarget){\r
124                         var elTgt = iwfGetOrCreateWithinForm(urlOrForm, 'iwfTarget', 'input', 'hidden');\r
125                         if (elTgt){\r
126                                 iwfAttribute(elTgt, 'value', localTarget);\r
127                                 iwfRemoveAttribute(elTgt, 'disabled');\r
128                         }\r
129                 }\r
130 \r
131 \r
132                 url = urlOrForm.action;\r
133                 method = urlOrForm.method.toUpperCase();\r
134                 switch(method){\r
135                         case "POST":\r
136                                 postdata = _iwfGetFormData(urlOrForm, url, ctl);\r
137 \r
138                                 // we also need to properly set the content-type header...\r
139                                 var frm = iwfGetForm(urlOrForm);\r
140                                 if (frm){\r
141                                         var enc = iwfAttribute(frm, 'encoding');\r
142                                         if (!enc){\r
143                                                 enc = iwfAttribute(frm, 'enctype');\r
144                                         }\r
145                                         if (enc){\r
146                                                 contentType = enc;\r
147                                         }\r
148                                 }\r
149 \r
150                                 break;\r
151                         case "GET":\r
152                         default:\r
153                                 url = _iwfGetFormData(urlOrForm, url, ctl);\r
154                                 break;\r
155                 }\r
156         }\r
157 \r
158         // prevent any browser caching of our url by requesting a unique url everytime...\r
159         url += ((url.indexOf('?') > -1) ? '&' : '?') + 'iwfRequestId=' + new Date().valueOf();\r
160 \r
161 iwfLog("url = " + url);\r
162 iwfLog("method = " + method);\r
163 iwfLog("postdata = " + postdata);\r
164 iwfLog("contenttype = " + contentType);\r
165 \r
166 \r
167         var req = null;\r
168         if (!addToHistory){\r
169 iwfLog("using XHR to perform request...");\r
170                 // use XHR to perform the request, as this will\r
171                 // prevent the browser from adding it to history.\r
172                 req = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");\r
173 \r
174                 // bind our callback\r
175                 req.onreadystatechange = iwfBindCallback;\r
176 \r
177                 // show progress if it's not already visible...\r
178                 _iwfOnRequestStart();\r
179 \r
180                 // hit the server\r
181                 req.open(method, url, true);\r
182                 req.setRequestHeader('Content-Type', contentType);\r
183                 req.send(postdata);\r
184         } else {\r
185                 // use an IFRAME element to perform the request,\r
186                 // as this will cause the browser to add it to history.\r
187                 // TODO: make this work!!!\r
188 iwfLog("using IFRAME to perform request...");\r
189 iwfLog('request and add to history not implemented yet!!!', true);\r
190                 return;\r
191 \r
192                 var el = iwfGetById("iwfHistoryFrame");\r
193                 if (!el){\r
194                         iwfLog("To enable history tracking in IWF, you must add an invisible IFRAME element to your page:\n<IFRAME id='iwfHistoryFrame' style='display:none'></IFRAME>", true);\r
195                 }\r
196 \r
197                 el.src = url;\r
198 \r
199                 // show progress if it's not already visible...\r
200                 _iwfOnRequestStart();\r
201 \r
202 \r
203         }\r
204 \r
205         // if this is called from the form.onsubmit event, make sure the form doesn't submit...\r
206 \r
207         if (isFromForm){\r
208                 return false;\r
209         } else {\r
210                 // return absolutely nothing so anchor tags don't hork the current page\r
211         }\r
212 \r
213 }\r
214 \r
215 function _iwfGetFormData(form, url, ctl){\r
216 \r
217         var method = form.method;\r
218         if (!method) method = "get";\r
219 \r
220         var output = null;\r
221 \r
222         if (method == 'get'){\r
223                 output = url + ((url.indexOf('?') > -1) ? '&' : '?');\r
224         } else {\r
225                 output = '';\r
226         }\r
227 \r
228 \r
229         // if there is a target specified in the <form> tag,\r
230         // copy its contents to a hidden <input type='hidden'> tag.\r
231 \r
232 iwfLog("total elements in form named '" + form.name + "': " + form.elements.length);\r
233         for(var i=0;i<form.elements.length;i++){\r
234                 var el = form.elements[i];\r
235                 var nm = iwfAttribute(el, 'name');\r
236                 var val = null;\r
237                 if (!iwfAttribute(el, 'disabled') && nm){\r
238                         switch (el.tagName.toLowerCase()){\r
239                                 case 'input':\r
240                                         switch(iwfAttribute(el, 'type')){\r
241                                                 case 'checkbox':\r
242                                                 case 'radio':\r
243                                                         if (iwfAttribute(el, 'checked')){\r
244                                                                 val = iwfAttribute(el, 'value');\r
245                                                         }\r
246                                                         break;\r
247                                                 case 'button':\r
248                                                         if (el == ctl){\r
249                                                                 val = iwfAttribute(el, 'value');\r
250                                                         }\r
251                                                         break;\r
252                                                 case 'submit':\r
253                                                         if (el == ctl){\r
254                                                                 val = iwfAttribute(el, 'value');\r
255                                                         }\r
256                                                         break;\r
257                                                 case 'text':\r
258                                                 default:\r
259                                                         val = iwfAttribute(el, 'value');\r
260                                                         break;\r
261                                                 case 'file':\r
262                                                         iwfLog('TODO: implement <input type="file"> in _iwfGetFormData', true);\r
263                                                         val = iwfAttribute(el, 'value');\r
264                                                         break;\r
265                                                 case 'image':\r
266                                                         iwfLog('TODO: implement <input type="image"> in _iwfGetFormData', true);\r
267                                                         val = iwfAttribute(el, 'value');\r
268                                                         break;\r
269                                                 case 'reset':\r
270                                                         iwfLog('TODO: implement <input type="reset"> in _iwfGetFormData', true);\r
271                                                         break;\r
272                                                 case 'hidden':\r
273                                                         val = iwfAttribute(el, 'value');\r
274                                                         break;\r
275                                         }\r
276                                         break;\r
277                                 case 'textarea':\r
278                                         val = iwfAttribute(el, 'innerText') || el.value;\r
279                                         break;\r
280                                 case 'button':\r
281                                         if (el == ctl){\r
282                                                 val = iwfAttribute(el, 'innerText') || el.value;\r
283                                         }\r
284                                         break;\r
285                                 case 'select':\r
286                                         for(var j=0;j<el.options.length;j++){\r
287                                                 if (iwfAttribute(el.options[j], 'selected') == 'true'){\r
288                                                         if (!val){\r
289                                                                 val = iwfAttribute(el.options[j], 'value');\r
290                                                         } else {\r
291                                                                 val += '+' + iwfAttribute(el.options[j], 'value');\r
292                                                         }\r
293                                                 }\r
294                                         }\r
295                         }\r
296 \r
297                         if (val){\r
298                                 if (output.length > 0){\r
299                                         output += '&';\r
300                                 }\r
301                                 output += escape(nm) + '=' + escape(val);\r
302                         }\r
303                 }\r
304         }\r
305         if (output.length == 0){\r
306                 return null;\r
307         } else {\r
308                 return output;\r
309         }\r
310 }\r
311 \r
312 function _iwfResponseReceived(doc){\r
313         iwfLog('iframeloaded');\r
314         var xmlDoc = new iwfXmlDoc(doc.innerHTML);\r
315 }\r
316 \r
317 function _iwfResponseHandler(origText, callback, tgt){\r
318         var doc = new iwfXmlDoc(origText);\r
319         if (!doc.response){\r
320                 // not in our default xml format.\r
321                 // just throw out the xml to the callback, if any\r
322                 if (callback){\r
323                         callback(doc);\r
324                 } else {\r
325                         iwfLog("IWF Ajax Error: No callback defined for non-standard response:\n" + origText, true);\r
326                 }\r
327         } else {\r
328                 if (doc.response.debugging == 'true'){\r
329                         iwfLoggingEnabled = true;\r
330                         iwfLog("IWF Ajax Debugging:\nParsed response:\n\n" + doc.response.outerXml(true), true);\r
331                         iwfShowLog();\r
332                 }\r
333                 for(var i=0; i< doc.response.childNodes.length; i++){\r
334                         var node = doc.response.childNodes[i];\r
335                         if (node.nodeName.indexOf("#") != 0){\r
336 iwfLog('node.target=' + node.target + '\ntgt=' + tgt);\r
337                                 if (!tgt) {\r
338                                         // server target is ignored if a client target exists.\r
339                                         tgt = node.target;\r
340                                 }\r
341                                 if (node.errorCode && iwfToInt(node.errorCode, true) != 0){\r
342                                         // an error occurred.\r
343                                         _iwfOnRequestError(node.errorCode, node.errorMessage);\r
344                                 } else {\r
345                                         if (!node.type){\r
346                                                 node.type = "";\r
347                                         }\r
348                                         switch(node.type.toLowerCase()){\r
349                                                 case "html":\r
350                                                 case "xhtml":\r
351                                                         var innerHtml = node.innerHtml();\r
352 //iwfLog('parsed html response:\n\n' + innerHtml);\r
353                                                         _iwfInsertHtml(innerHtml, tgt);\r
354                                                         break;\r
355                                                 case "javascript":\r
356                                                 case "js":\r
357                                                         var bomb = true;\r
358                                                         if (node.childNodes.length == 1){\r
359                                                                 var js = node.childNodes[0];\r
360                                                                 if (js.nodeName == '#cdata' || js.nodeName == '#comment'){\r
361                                                                         bomb = false;\r
362                                                                         var code = js.getText();\r
363                                                                         eval(code);\r
364                                                                 }\r
365                                                         }\r
366                                                         if (bomb){\r
367                                                                 iwfLog("IWF Ajax Error: When an <action> is defined of type javascript, it content must be contained by either a Comment (<!-- -->) or CDATA (<![CDATA[  ]]>).\nCDATA is the more appropriate manner, as this is the xml-compliant one.\nHowever, COMMENT is also allowed as this is html-compliant, such as within a <script> element.\n\n<action> xml returned:\n\n" + node.outerXml(), true);\r
368                                                         }\r
369                                                         break;\r
370                                                 case "xml":\r
371                                                         if (callback){\r
372                                                                 callback(node);\r
373                                                         }\r
374                                                         break;\r
375                                                 case "debug":\r
376                                                         iwfLog("IWF Debug: <action> type identified as 'debug'.\nXml received for current action:\n\n" + node.outerXml(), true);\r
377                                                         break;\r
378                                                 default:\r
379                                                         iwfLog('IWF Ajax Error: <action> type of "' + node.type + '" is not a valid option.\n\nValid options:\n\'html\' or \'xhtml\' = parse as html and inject into element with the id specified by the target attribute\n\'javascript\' or \'js\' = parse as javascript and execute\n\'xml\' = parse as xml and call the callback specified when iwfRequest() was called\n\'debug\' = parse as xml and log/alert the result\n\n<action> xml returned:\n\n' + node.outerXml(), true);\r
380                                                         break;\r
381                                         }\r
382                                 }\r
383                         }\r
384                 }\r
385         }\r
386 \r
387 }\r
388 \r
389 function _iwfInsertHtml(html, parentNodeId){\r
390         if(!parentNodeId){\r
391                 parentNodeId = 'iwfContent';\r
392                 iwfLog("IWF Ajax Warning: <action> with a type of 'html' does not have its target attribute specified, so using the default of 'iwfContent'.");\r
393         }\r
394 \r
395         if(!parentNodeId){\r
396                         iwfLog("IWF Ajax Error: <action> node with a type of 'html' does not have its target attribute specified to a valid element.\nPlease specify the id of the element into which the contents of the <action> node should be placed.\nTo fill the entire page, simply specify 'body'.", true);\r
397         } else {\r
398                 var el = iwfGetById(parentNodeId);\r
399                 if (!el){\r
400                         if (parentNodeId == 'body'){\r
401                                 el = iwfGetByTagName('body');\r
402                                 if (!el || el.length == 0){\r
403                                         iwfLog("IWF Ajax Error: Could not locate the tag named 'body'", true);\r
404                                         return;\r
405                                 } else {\r
406                                         el = el[0];\r
407                                 }\r
408                         } else {\r
409                                         iwfLog('IWF Ajax Error: Could not locate element with id of ' + parentNodeId + ' into which the following html should be placed:\n\n' + html, true);\r
410                                         return;\r
411                         }\r
412                 }\r
413 \r
414 //iwfLog(iwfElementToString(el));\r
415 //iwfLog(html);\r
416 \r
417                 // trying to shove a <form> node inside another <form> node is a bad thing.\r
418                 // make sure we don't do that here.\r
419                 var re = /<form/i;\r
420                 var match = re.exec(html);\r
421                 if (match && document.forms.length > 0){\r
422                         // our html to inject contains a form node.\r
423                         // bubble up the chain until we find a <form> node, or we have no parents\r
424                         var elParent = el;\r
425                         while (elParent && elParent.tagName.toLowerCase() != 'form'){\r
426                                 elParent = iwfGetParent(elParent);\r
427                         }\r
428 \r
429                         if (elParent && elParent.tagName.toLowerCase() == 'form'){\r
430                                 iwfLog('IWF Ajax Error: Attempting to inject html which contains a <form> node into a target element which is itself a <form> node, or is already contained by a <form> node.\nThis is bad html, and will not work appropriately on some major browsers.', true);\r
431                         }\r
432                 }\r
433 \r
434 \r
435                 el.innerHTML = html;\r
436 \r
437 \r
438                 // if there is a script element, we have to explicitly add it to the body element,\r
439                 // as IE and firefox just don't like it when you try to add it as part of the innerHTML\r
440                 // property.  Go figure.\r
441 \r
442                 var i = 0;\r
443                 // don't stomp on any existing scripts...\r
444                 while (iwfGetById('iwfScript' + i)){\r
445                         i++;\r
446                 }\r
447 \r
448                 var scriptStart = html.indexOf("<script");\r
449                 while(scriptStart > -1){\r
450                         scriptStart = html.indexOf(">", scriptStart) + 1;\r
451 \r
452                         // copy contents of script into a default holder\r
453                         var scriptEnd = html.indexOf("</script>", scriptStart);\r
454                         var scriptHtml = html.substr(scriptStart, scriptEnd - scriptStart);\r
455 \r
456                         var re = /^\s*<!--/;\r
457                         var match = re.exec(scriptHtml);\r
458                         if (!match){\r
459                                 iwfLog("IWF Ajax Error: Developer, you *must* put the <!-- and //--> 'safety net' around your script within all <script> tags.\nThe offending <script> tag contains the following code:\n\n" + scriptHtml + "\n\nThis requirement is due to how IWF injects the html so the browser is able to actually parse the script and make its contents available for execution.", true);\r
460                         }\r
461 \r
462                         // this code is the worst hack in this entire framework.\r
463                         // the code in the try portion should work anywhere -- but\r
464                         // ie barfs when you try to set the innerHTML on a script element.\r
465                         // not only that, but ie won't parse script unless a visible element\r
466                         // is contained in the innerHTML when it is set, so we need the <code>ie hack here</code>\r
467                         // and it cannot be removed.\r
468                         // I don't understand why creating a new node and setting its innerHTML causes the browsers\r
469                         // to parse and execute the script, but it does.\r
470                         // Note there is a major leak here, as we do not try to reuse existing iwfScriptXXXX elements\r
471                         // in case they are in use by other targets.  To clean these up, simply call iwfCleanScripts\r
472                         // periodically.  This is so app-dependent, I didn't want to try to keep track of everything\r
473                         // and possibly miss a case, causing really weird results that only show up occassionally.\r
474                         //\r
475                         // Plus I'm getting lazy. :)\r
476                         //\r
477                         try {\r
478                                 // moz (DOM)\r
479                                 var elScript = iwfGetOrCreateById('iwfScript' + i, 'script', 'body');\r
480                                 elScript.type = 'text/javascript';\r
481                                 elScript.defer = 'true';\r
482                                 elScript.innerHTML = scriptHtml;\r
483 \r
484                         } catch(e){\r
485                                 // ie hack -- need a visible tag within a non-script element to have scripting apply... Don't ask me why, ask the IE team why...\r
486                                 var elDiv = iwfGetOrCreateById('iwfScript' + i, '<div style="display:none"></div>', 'body');\r
487                                 elDiv.innerHTML = '<code>ie hack here</code><script id="iwfScript' + i + '" defer="true">' + scriptHtml + '</script' + '>';\r
488                         }\r
489 \r
490                         i++;\r
491 \r
492                         scriptStart = html.indexOf("<script", scriptEnd+8);\r
493                 }\r
494         }\r
495 }\r
496 \r
497 function iwfCleanScripts(){\r
498         var i = 0;\r
499         while((el = iwfGetById('iwfScript' + i))){\r
500                 iwfRemoveNode(el);\r
501                 i++;\r
502         }\r
503 }\r
504 \r
505 \r
506 \r
507 var _iwfTotalRequests = 0;\r
508 var _iwfPendingRequests = 0;\r
509 var _iwfRequestTicker = null;\r
510 var _iwfRequestTickCount = 0;\r
511 var _iwfRequestTickDuration = 100;\r
512 function _iwfOnRequestStart(){\r
513         _iwfPendingRequests++;\r
514         _iwfTotalRequests++;\r
515 \r
516         _iwfRequestTickDuration = 100;\r
517 \r
518         if (!_iwfRequestTicker){\r
519                 _iwfRequestTickCount = 0;\r
520                 if (window.iwfOnRequestStart){\r
521                         _iwfRequestTickDuration = iwfOnRequestStart();\r
522                 } else {\r
523                         // use default busy implementation...\r
524                         // TODO: make this better!\r
525                         window.status = 'busy.';\r
526                 }\r
527                 if (!_iwfRequestTickDuration){\r
528                         _iwfRequestTickDuration = 100;\r
529                 }\r
530                 _iwfRequestTicker = setInterval(_iwfOnRequestTick, _iwfRequestTickDuration);\r
531         }\r
532 }\r
533 \r
534 function _iwfOnRequestTick(){\r
535         _iwfRequestTickCount++;\r
536         if (window.iwfOnRequestTick){\r
537                 iwfOnRequestTick(_iwfRequestTickCount, _iwfRequestTickDuration, _iwfPendingRequests);\r
538         } else if (!window.iwfOnRequestStart) {\r
539                 // use default busy implementation...\r
540                 // TODO: make this better!\r
541                 window.status = 'busy...............................................'.substr(0, (_iwfRequestTickCount % 45) + 5);\r
542         } else {\r
543                 // they didn't define a tick function,\r
544                 // but they did define a start one, so do nothing.\r
545         }\r
546 }\r
547 \r
548 function _iwfOnRequestEnd(){\r
549         _iwfPendingRequests--;\r
550         if (_iwfPendingRequests < 1){\r
551                 _iwfPendingRequests = 0;\r
552                 _iwfTotalRequests = 0;\r
553                 clearInterval(_iwfRequestTicker);\r
554                 _iwfRequestTicker = null;\r
555                 if (window.iwfOnRequestEnd){\r
556                         iwfOnRequestEnd();\r
557                 } else if (!window.iwfOnRequestStart) {\r
558                         // use default busy implementation...\r
559                         // TODO: make this better!\r
560                         window.status = 'done.';\r
561                 } else {\r
562                         // they didn't define an end function,\r
563                         // but they did define a start one, so do nothing.\r
564                 }\r
565 \r
566         } else {\r
567                 if (window.iwfOnRequestProgress){\r
568                         iwfOnRequestProgress(_iwfPendingRequests, _iwfTotalRequests);\r
569                 } else if (!window.iwfOnRequestStart) {\r
570                         // use default busy implementation...\r
571                         // TODO: make this better!\r
572                         window.status = 'Remaining: ' + _iwfPendingRequests;\r
573                 } else {\r
574                         // they didn't define an end function,\r
575                         // but they did define a start one, so do nothing.\r
576                 }\r
577         }\r
578 }\r
579 \r
580 function _iwfOnRequestError(code, msg, text){\r
581         iwfLog("Error " + code + ": " + msg + ":\n\n" + text);\r
582         if (window.iwfOnRequestError){\r
583                 iwfOnRequestError(code, msg, text);\r
584         } else {\r
585                 alert("Error " + code + ": " + msg + ":\n\n" + text);\r
586         }\r
587 }\r
588 \r
589 // -----------------------------------\r
590 // End: AJAX Request and Response\r
591 // -----------------------------------\r