1 // -----------------------------------------------------------------------------
\r
2 // IWF - Interactive Website Framework. Javascript library for creating
\r
3 // responsive thin client interfaces.
\r
5 // Copyright (C) 2005 Brock Weaver brockweaver@gmail.com
\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
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
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
22 // brockweaver@gmail.com
\r
25 // -----------------------------------------------------------------------------
\r
27 // --------------------------------------------------------------------------
\r
30 // Thread-safe background xml request via XmlHttpRequest object
\r
35 // iwfgui.js (optional -- pretty progress bar)
\r
37 // Brock Weaver - brockweaver@sourceforge.net - iwf.sourceforge.net
\r
38 // v 0.1 - 2005-06-05
\r
40 // --------------------------------------------------------------------------
\r
42 // AddToHistory does not work
\r
43 // Iframe not implemented
\r
44 // --------------------------------------------------------------------------
\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
50 // -----------------------------------
\r
51 // Begin: AJAX Request and Response
\r
52 // -----------------------------------
\r
54 function iwfRequest(urlOrForm, targetElementOnResponse, addToHistory, callback){
\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
61 function iwfBindCallback(){
\r
62 if (req.readyState == 4) {
\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
68 _iwfOnRequestError(req.status, req.statusText, req.responseText);
\r
74 // determine how to hit the server...
\r
77 var postdata = null;
\r
78 var isFromForm = true;
\r
79 var contentType = 'application/x-www-form-urlencoded';
\r
81 if (iwfIsString(urlOrForm)){
\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
87 var frm = iwfGetForm(urlOrForm);
\r
94 // is name of a form.
\r
95 // fill with the form object.
\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
105 if (!iwfIsString(urlOrForm)){
\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
114 urlOrForm = urlOrForm.form;
\r
118 // if they passed a form and no local target, lookup the form.iwfTarget attribute and use it if possible
\r
120 localTarget = iwfAttribute(urlOrForm, 'iwfTarget');
\r
124 var elTgt = iwfGetOrCreateWithinForm(urlOrForm, 'iwfTarget', 'input', 'hidden');
\r
126 iwfAttribute(elTgt, 'value', localTarget);
\r
127 iwfRemoveAttribute(elTgt, 'disabled');
\r
132 url = urlOrForm.action;
\r
133 method = urlOrForm.method.toUpperCase();
\r
136 postdata = _iwfGetFormData(urlOrForm, url, ctl);
\r
138 // we also need to properly set the content-type header...
\r
139 var frm = iwfGetForm(urlOrForm);
\r
141 var enc = iwfAttribute(frm, 'encoding');
\r
143 enc = iwfAttribute(frm, 'enctype');
\r
153 url = _iwfGetFormData(urlOrForm, url, ctl);
\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
161 iwfLog("url = " + url);
\r
162 iwfLog("method = " + method);
\r
163 iwfLog("postdata = " + postdata);
\r
164 iwfLog("contenttype = " + contentType);
\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
174 // bind our callback
\r
175 req.onreadystatechange = iwfBindCallback;
\r
177 // show progress if it's not already visible...
\r
178 _iwfOnRequestStart();
\r
181 req.open(method, url, true);
\r
182 req.setRequestHeader('Content-Type', contentType);
\r
183 req.send(postdata);
\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
192 var el = iwfGetById("iwfHistoryFrame");
\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
199 // show progress if it's not already visible...
\r
200 _iwfOnRequestStart();
\r
205 // if this is called from the form.onsubmit event, make sure the form doesn't submit...
\r
210 // return absolutely nothing so anchor tags don't hork the current page
\r
215 function _iwfGetFormData(form, url, ctl){
\r
217 var method = form.method;
\r
218 if (!method) method = "get";
\r
222 if (method == 'get'){
\r
223 output = url + ((url.indexOf('?') > -1) ? '&' : '?');
\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
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
237 if (!iwfAttribute(el, 'disabled') && nm){
\r
238 switch (el.tagName.toLowerCase()){
\r
240 switch(iwfAttribute(el, 'type')){
\r
243 if (iwfAttribute(el, 'checked')){
\r
244 val = iwfAttribute(el, 'value');
\r
249 val = iwfAttribute(el, 'value');
\r
254 val = iwfAttribute(el, 'value');
\r
259 val = iwfAttribute(el, 'value');
\r
262 iwfLog('TODO: implement <input type="file"> in _iwfGetFormData', true);
\r
263 val = iwfAttribute(el, 'value');
\r
266 iwfLog('TODO: implement <input type="image"> in _iwfGetFormData', true);
\r
267 val = iwfAttribute(el, 'value');
\r
270 iwfLog('TODO: implement <input type="reset"> in _iwfGetFormData', true);
\r
273 val = iwfAttribute(el, 'value');
\r
278 val = iwfAttribute(el, 'innerText') || el.value;
\r
282 val = iwfAttribute(el, 'innerText') || el.value;
\r
286 for(var j=0;j<el.options.length;j++){
\r
287 if (iwfAttribute(el.options[j], 'selected') == 'true'){
\r
289 val = iwfAttribute(el.options[j], 'value');
\r
291 val += '+' + iwfAttribute(el.options[j], 'value');
\r
298 if (output.length > 0){
\r
301 output += escape(nm) + '=' + escape(val);
\r
305 if (output.length == 0){
\r
312 function _iwfResponseReceived(doc){
\r
313 iwfLog('iframeloaded');
\r
314 var xmlDoc = new iwfXmlDoc(doc.innerHTML);
\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
325 iwfLog("IWF Ajax Error: No callback defined for non-standard response:\n" + origText, true);
\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
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
338 // server target is ignored if a client target exists.
\r
341 if (node.errorCode && iwfToInt(node.errorCode, true) != 0){
\r
342 // an error occurred.
\r
343 _iwfOnRequestError(node.errorCode, node.errorMessage);
\r
348 switch(node.type.toLowerCase()){
\r
351 var innerHtml = node.innerHtml();
\r
352 //iwfLog('parsed html response:\n\n' + innerHtml);
\r
353 _iwfInsertHtml(innerHtml, tgt);
\r
358 if (node.childNodes.length == 1){
\r
359 var js = node.childNodes[0];
\r
360 if (js.nodeName == '#cdata' || js.nodeName == '#comment'){
\r
362 var code = js.getText();
\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
376 iwfLog("IWF Debug: <action> type identified as 'debug'.\nXml received for current action:\n\n" + node.outerXml(), true);
\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
389 function _iwfInsertHtml(html, 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
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
398 var el = iwfGetById(parentNodeId);
\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
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
414 //iwfLog(iwfElementToString(el));
\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
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
425 while (elParent && elParent.tagName.toLowerCase() != 'form'){
\r
426 elParent = iwfGetParent(elParent);
\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
435 el.innerHTML = html;
\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
443 // don't stomp on any existing scripts...
\r
444 while (iwfGetById('iwfScript' + i)){
\r
448 var scriptStart = html.indexOf("<script");
\r
449 while(scriptStart > -1){
\r
450 scriptStart = html.indexOf(">", scriptStart) + 1;
\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
456 var re = /^\s*<!--/;
\r
457 var match = re.exec(scriptHtml);
\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
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
475 // Plus I'm getting lazy. :)
\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
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
492 scriptStart = html.indexOf("<script", scriptEnd+8);
\r
497 function iwfCleanScripts(){
\r
499 while((el = iwfGetById('iwfScript' + i))){
\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
516 _iwfRequestTickDuration = 100;
\r
518 if (!_iwfRequestTicker){
\r
519 _iwfRequestTickCount = 0;
\r
520 if (window.iwfOnRequestStart){
\r
521 _iwfRequestTickDuration = iwfOnRequestStart();
\r
523 // use default busy implementation...
\r
524 // TODO: make this better!
\r
525 window.status = 'busy.';
\r
527 if (!_iwfRequestTickDuration){
\r
528 _iwfRequestTickDuration = 100;
\r
530 _iwfRequestTicker = setInterval(_iwfOnRequestTick, _iwfRequestTickDuration);
\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
543 // they didn't define a tick function,
\r
544 // but they did define a start one, so do nothing.
\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
557 } else if (!window.iwfOnRequestStart) {
\r
558 // use default busy implementation...
\r
559 // TODO: make this better!
\r
560 window.status = 'done.';
\r
562 // they didn't define an end function,
\r
563 // but they did define a start one, so do nothing.
\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
574 // they didn't define an end function,
\r
575 // but they did define a start one, so do nothing.
\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
585 alert("Error " + code + ": " + msg + ":\n\n" + text);
\r
589 // -----------------------------------
\r
590 // End: AJAX Request and Response
\r
591 // -----------------------------------
\r