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@users.sourceforge.net
\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@users.sourceforge.net
\r
23 /// 1605 NW Maple Pl
\r
24 /// Ankeny, IA 50021
\r
26 //! http://iwf.sourceforge.net/
\r
27 // --------------------------------------------------------------------------
\r
28 //! NOTE: To minimize file size, strip all fluffy comments (except the LGPL, of course!)
\r
29 //! using the following regex (global flag and multiline on):
\r
30 //! ^\t*//([^/!].*|$)
\r
32 // This reduces file size by about 30%, give or take.
\r
34 //! To rip out only logging statements (commented or uncommented):
\r
36 // --------------------------------------------------------------------------
\r
39 // --------------------------------------------------------------------------
\r
43 // Thread-safe background xml request via XmlHttpRequest object
\r
48 //! iwfgui.js (optional -- pretty progress bar)
\r
50 //! Brock Weaver - brockweaver@users.sourceforge.net
\r
51 //! v 0.2 - 2005-11-14
\r
53 // --------------------------------------------------------------------------
\r
54 //! v 0.1 - 2005-06-05
\r
55 //! Initial release.
\r
58 //! AddToHistory does not work
\r
59 //! Iframe not implemented
\r
61 // =========================================================================
\r
66 // show progress bar if they included iwfgui.js, window.status otherwise.
\r
67 var _iwfShowGuiProgress = iwfExists(window.iwfShow);
\r
74 // -----------------------------------
\r
75 // Begin: Dependency Check
\r
76 // -----------------------------------
\r
78 if (!window.iwfGetById || !window.iwfXmlDoc){
\r
79 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
82 // -----------------------------------
\r
83 // End: Dependency Check
\r
84 // -----------------------------------
\r
86 // -----------------------------------
\r
87 // Begin: AJAX Request and Response
\r
88 // -----------------------------------
\r
90 function iwfRequest(urlOrForm, targetElementOnResponse, addToHistory, callback){
\r
92 // we use a javascript feature here called "inner functions"
\r
93 // using these means the local variables retain their values after the outer function
\r
94 // has returned. this is useful for thread safety, so
\r
95 // reassigning the onreadystatechange function doesn't stomp over earlier requests.
\r
97 function iwfBindCallback(){
\r
98 if (req.readyState == 4) {
\r
100 if (req.status == 200 || req.status == 0) {
\r
101 //iwfLog('exact response from server:\n\n' + req.responseText);
\r
102 _iwfResponseHandler(req.responseText, localCallback, localTarget);
\r
104 _iwfOnRequestError(req.status, req.statusText, req.responseText);
\r
110 // determine how to hit the server...
\r
112 var method = 'GET';
\r
113 var postdata = null;
\r
114 var isFromForm = true;
\r
115 var contentType = 'application/x-www-form-urlencoded';
\r
117 if (iwfIsString(urlOrForm)){
\r
119 // if we get here, they either specified the url or the name of the form.
\r
120 // either way, flag so we return nothing.
\r
121 isFromForm = false;
\r
123 var frm = iwfGetForm(urlOrForm);
\r
130 // is name of a form.
\r
131 // fill with the form object.
\r
137 // use a local variable to hold our callback and target until the inner function is called...
\r
138 var localCallback = callback;
\r
139 var localTarget = targetElementOnResponse;
\r
141 if (!iwfIsString(urlOrForm)){
\r
146 // is a form or a control in the form.
\r
147 if (iwfExists(urlOrForm.form)){
\r
148 // is a control in the form. jump up to the form.
\r
150 urlOrForm = urlOrForm.form;
\r
154 // if they passed a form and no local target, lookup the form.iwfTarget attribute and use it if possible
\r
156 localTarget = iwfAttribute(urlOrForm, 'iwfTarget');
\r
160 var elTgt = iwfGetOrCreateByNameWithinForm(urlOrForm, 'iwfTarget', 'input', 'hidden');
\r
162 iwfAttribute(elTgt, 'value', localTarget);
\r
163 iwfRemoveAttribute(elTgt, 'disabled');
\r
168 url = urlOrForm.action;
\r
169 method = urlOrForm.method.toUpperCase();
\r
172 postdata = _iwfGetFormData(urlOrForm, url, ctl);
\r
174 // we also need to properly set the content-type header...
\r
175 var frm = iwfGetForm(urlOrForm);
\r
177 var enc = iwfAttribute(frm, 'encoding');
\r
179 enc = iwfAttribute(frm, 'enctype');
\r
189 url = _iwfGetFormData(urlOrForm, url, ctl);
\r
194 // prevent any browser caching of our url by requesting a unique url everytime...
\r
195 url += ((url.indexOf('?') > -1) ? '&' : '?') + 'iwfRequestId=' + new Date().valueOf();
\r
197 //iwfLog("url = " + url);
\r
198 //iwfLog("method = " + method);
\r
199 //iwfLog("postdata = " + postdata);
\r
200 //iwfLog("contenttype = " + contentType);
\r
204 if (!addToHistory){
\r
205 //iwfLog("using XHR to perform request...");
\r
206 // use XHR to perform the request, as this will
\r
207 // prevent the browser from adding it to history.
\r
208 req = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
\r
210 // bind our callback
\r
211 req.onreadystatechange = iwfBindCallback;
\r
213 // show progress if it's not already visible...
\r
214 _iwfOnRequestStart();
\r
217 req.open(method, url, true);
\r
218 req.setRequestHeader('Content-Type', contentType);
\r
219 req.send(postdata);
\r
221 // use an IFRAME element to perform the request,
\r
222 // as this will cause the browser to add it to history.
\r
223 // TODO: make this work!!!
\r
224 iwfLog("using IFRAME to perform request...");
\r
225 iwfLog('request and add to history not implemented yet!!!', true);
\r
228 var el = iwfGetById("iwfHistoryFrame");
\r
230 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
235 // show progress if it's not already visible...
\r
236 _iwfOnRequestStart();
\r
241 // if this is called from the form.onsubmit event, make sure the form doesn't submit...
\r
246 // return absolutely nothing so anchor tags don't hork the current page
\r
251 function _iwfGetFormData(form, url, ctl){
\r
253 var method = form.method;
\r
254 if (!method) method = "get";
\r
258 if (method == 'get'){
\r
259 output = url + ((url.indexOf('?') > -1) ? '&' : '?');
\r
265 // if there is a target specified in the <form> tag,
\r
266 // copy its contents to a hidden <input type='hidden'> tag.
\r
268 //iwfLog("total elements in form named '" + form.name + "': " + form.elements.length);
\r
269 for(var i=0;i<form.elements.length;i++){
\r
270 var el = form.elements[i];
\r
271 var nm = iwfAttribute(el, 'name');
\r
273 if (!iwfAttribute(el, 'disabled') && nm){
\r
274 switch (el.tagName.toLowerCase()){
\r
276 switch(iwfAttribute(el, 'type')){
\r
278 if (iwfAttribute(el, 'checked')){
\r
282 if (iwfAttribute(el, 'checked')){
\r
283 val = iwfAttribute(el, 'value');
\r
288 val = iwfAttribute(el, 'value');
\r
293 val = iwfAttribute(el, 'value');
\r
298 val = iwfAttribute(el, 'value');
\r
301 iwfLog('TODO: implement <input type="file"> in _iwfGetFormData', true);
\r
302 val = iwfAttribute(el, 'value');
\r
305 iwfLog('TODO: implement <input type="image"> in _iwfGetFormData', true);
\r
306 val = iwfAttribute(el, 'value');
\r
309 iwfLog('TODO: implement <input type="reset"> in _iwfGetFormData', true);
\r
312 val = iwfAttribute(el, 'value');
\r
317 val = iwfAttribute(el, 'innerText') || el.value;
\r
321 val = iwfAttribute(el, 'innerText') || el.value;
\r
325 for(var j=0;j<el.options.length;j++){
\r
326 if (iwfAttribute(el.options[j], 'selected') == 'true'){
\r
328 val = iwfAttribute(el.options[j], 'value');
\r
330 val += '+' + iwfAttribute(el.options[j], 'value');
\r
337 if (output.length > 0){
\r
340 output += escape(nm) + '=' + escape(val);
\r
344 if (output.length == 0){
\r
351 function _iwfResponseReceived(doc){
\r
352 iwfLog('iframeloaded');
\r
353 var xmlDoc = new iwfXmlDoc(doc.innerHTML);
\r
356 function _iwfResponseHandler(origText, callback, tgt){
\r
357 var doc = new iwfXmlDoc(origText);
\r
358 if (!doc.response){
\r
359 // not in our default xml format.
\r
360 // just throw out the xml to the callback, if any
\r
364 iwfLog("IWF Ajax Error: No callback defined for non-standard response:\n" + origText, true);
\r
367 if (doc.response.debugging == 'true'){
\r
368 iwfLoggingEnabled = true;
\r
369 iwfLog("IWF Ajax Debugging:\nParsed response:\n\n" + doc.response.outerXml(true), true);
\r
372 for(var i=0; i< doc.response.childNodes.length; i++){
\r
373 var node = doc.response.childNodes[i];
\r
374 if (node.nodeName.indexOf("#") != 0){
\r
375 //iwfLog('node.target=' + node.target + '\ntgt=' + tgt);
\r
377 // server target is ignored if a client target exists.
\r
380 if (node.errorCode && iwfToInt(node.errorCode, true) != 0){
\r
381 // an error occurred.
\r
382 _iwfOnRequestError(node.errorCode, node.errorMessage);
\r
387 switch(node.type.toLowerCase()){
\r
390 var innerHtml = node.innerHtml();
\r
391 //iwfLog('parsed html response:\n\n' + innerHtml);
\r
392 _iwfInsertHtml(innerHtml, tgt);
\r
397 if (node.childNodes.length == 1){
\r
398 var js = node.childNodes[0];
\r
399 if (js.nodeName == '#cdata' || js.nodeName == '#comment'){
\r
401 var code = js.getText();
\r
406 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
415 iwfLog("IWF Debug: <action> type identified as 'debug'.\nXml received for current action:\n\n" + node.outerXml(), true);
\r
418 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
428 function _iwfInsertHtml(html, parentNodeId){
\r
430 parentNodeId = 'iwfContent';
\r
431 iwfLog("IWF Ajax Warning: <action> with a type of 'html' does not have its target attribute specified, so using the default of 'iwfContent'.");
\r
435 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
437 var el = iwfGetById(parentNodeId);
\r
439 if (parentNodeId == 'body'){
\r
440 el = iwfGetByTagName('body');
\r
441 if (!el || el.length == 0){
\r
442 iwfLog("IWF Ajax Error: Could not locate the tag named 'body'", true);
\r
448 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
453 //iwfLog(iwfElementToString(el));
\r
456 // trying to shove a <form> node inside another <form> node is a bad thing.
\r
457 // make sure we don't do that here.
\r
459 var match = re.exec(html);
\r
460 if (match && document.forms.length > 0){
\r
461 // our html to inject contains a form node.
\r
462 // bubble up the chain until we find a <form> node, or we have no parents
\r
464 // I have doubts about adding elParent.tagName here
\r
465 // but I don't have better idea. -dpavlin
\r
466 while (elParent && elParent.tagName && elParent.tagName.toLowerCase() != 'form'){
\r
467 elParent = iwfGetParent(elParent);
\r
470 if (elParent && elParent.tagName && elParent.tagName.toLowerCase() == 'form'){
\r
471 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
476 el.innerHTML = html;
\r
479 // if there is a script element, we have to explicitly add it to the body element,
\r
480 // as IE and firefox just don't like it when you try to add it as part of the innerHTML
\r
481 // property. Go figure.
\r
484 // don't stomp on any existing scripts...
\r
485 while (iwfGetById('iwfScript' + i)){
\r
489 var scriptStart = html.indexOf("<script");
\r
490 while(scriptStart > -1){
\r
491 scriptStart = html.indexOf(">", scriptStart) + 1;
\r
493 // copy contents of script into a default holder
\r
494 var scriptEnd = html.indexOf("</script>", scriptStart);
\r
495 var scriptHtml = html.substr(scriptStart, scriptEnd - scriptStart);
\r
497 var re = /^\s*<!--/;
\r
498 var match = re.exec(scriptHtml);
\r
500 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
503 // this code is the worst hack in this entire framework.
\r
504 // the code in the try portion should work anywhere -- but
\r
505 // IE barfs when you try to set the innerHTML on a script element.
\r
506 // not only that, but IE won't parse script unless a visible element
\r
507 // is contained in the innerHTML when it is set, so we need the <code>IE hack here</code>
\r
508 // and it cannot be removed.
\r
509 // I don't understand why creating a new node and setting its innerHTML causes the browsers
\r
510 // to parse and execute the script, but it does.
\r
511 // Note there is a major leak here, as we do not try to reuse existing iwfScriptXXXX elements
\r
512 // in case they are in use by other targets. To clean these up, simply call iwfCleanScripts
\r
513 // periodically. This is so app-dependent, I didn't want to try to keep track of everything
\r
514 // and possibly miss a case, causing really weird results that only show up occassionally.
\r
516 // Plus I'm getting lazy. :)
\r
520 var elScript = iwfGetOrCreateById('iwfScript' + i, 'script');
\r
521 elScript.type = 'text/javascript';
\r
522 elScript.defer = 'true';
\r
523 elScript.innerHTML = scriptHtml;
\r
525 iwfAppendChild('body', elScript);
\r
528 //iwfLog("IE Hack for injecting script tag...", true);
\r
530 // IE needs a visible tag within a non-script element to have scripting apply... Don't ask me why, ask the IE team why.
\r
531 // My guess is the visible element causes the page to at least partially re-render, which in turn kicks off any script parsing
\r
533 var elDiv = iwfGetOrCreateById('iwfScript' + i, '<div style="display:none"></div>', 'body');
\r
534 elDiv.innerHTML = '<code>IE hack here</code><script id="iwfScript' + i + '" defer="true">' + scriptHtml + '</script' + '>';
\r
539 scriptStart = html.indexOf("<script", scriptEnd+8);
\r
544 function iwfCleanScripts(){
\r
546 while(iwfRemoveNode('iwfScript' + (i++)));
\r
552 var _iwfTotalRequests = 0;
\r
553 var _iwfPendingRequests = 0;
\r
554 var _iwfRequestTicker = null;
\r
555 var _iwfRequestTickCount = 0;
\r
556 var _iwfRequestTickDuration = 100;
\r
559 // TODO: make the styles applied be configurable variables at the top of this file.
\r
560 function _iwfGetBusyBar(inner){
\r
561 var b = iwfGetById('iwfBusy');
\r
563 b = iwfGetOrCreateById('iwfBusy', 'div', 'body');
\r
565 b.style.position = 'absolute';
\r
566 b.style.border = '1px solid black';
\r
567 b.style.backgroundColor = '#efefef';
\r
568 b.style.textAlign = 'center';
\r
572 iwfZIndex(b, 9999);
\r
577 // iwfX(b, iwfClientWidth() - iwfWidth(b)-5);
\r
578 // iwfY(b, iwfClientHeight() - iwfHeight(b)-5);
\r
585 var bb = iwfGetById('iwfBusyBar');
\r
587 bb = iwfGetOrCreateById('iwfBusyBar', 'div', b);
\r
588 bb.style.backgroundColor = 'navy';
\r
589 bb.style.color = 'white';
\r
590 bb.style.textAlign = 'center';
\r
605 function _iwfOnRequestStart(){
\r
606 _iwfPendingRequests++;
\r
607 _iwfTotalRequests++;
\r
609 _iwfRequestTickDuration = 100;
\r
611 if (!_iwfRequestTicker){
\r
612 _iwfRequestTickCount = 0;
\r
613 if (window.iwfOnRequestStart){
\r
614 _iwfRequestTickDuration = iwfOnRequestStart();
\r
615 } else if (_iwfShowGuiProgress) {
\r
616 // use gui busy implementation
\r
617 var bb = _iwfGetBusyBar(true);
\r
619 bb.innerHTML = '0%';
\r
620 iwfShow(_iwfGetBusyBar(false));
\r
622 // use default busy implementation...
\r
623 window.status = 'busy.';
\r
625 if (!_iwfRequestTickDuration){
\r
626 _iwfRequestTickDuration = 100;
\r
628 _iwfRequestTicker = setInterval(_iwfOnRequestTick, _iwfRequestTickDuration);
\r
632 function _iwfOnRequestTick(){
\r
633 _iwfRequestTickCount++;
\r
634 if (window.iwfOnRequestTick){
\r
635 iwfOnRequestTick(_iwfRequestTickCount, _iwfRequestTickDuration, _iwfPendingRequests);
\r
636 } else if (!window.iwfOnRequestStart) {
\r
637 if (_iwfShowGuiProgress) {
\r
638 // use gui busy implementation
\r
639 var bar = _iwfGetBusyBar(true);
\r
641 var w = iwfWidth(bar) + 1;
\r
646 bar.innerHTML = "loading " + iwfIntFormat(w) + "%";
\r
649 // use default busy implementation...
\r
650 window.status = 'busy...............................................'.substr(0, (_iwfRequestTickCount % 45) + 5);
\r
653 // they didn't define a tick function,
\r
654 // but they did define a start one, so do nothing.
\r
658 function _iwfOnRequestEnd(){
\r
659 _iwfPendingRequests--;
\r
660 if (_iwfPendingRequests < 1){
\r
661 _iwfPendingRequests = 0;
\r
662 _iwfTotalRequests = 0;
\r
663 clearInterval(_iwfRequestTicker);
\r
664 _iwfRequestTicker = null;
\r
665 if (window.iwfOnRequestEnd){
\r
667 } else if (!window.iwfOnRequestStart) {
\r
668 if (_iwfShowGuiProgress) {
\r
669 // use gui busy implementation
\r
670 var bar = _iwfGetBusyBar(true);
\r
672 iwfWidth(bar, 100);
\r
673 bar.innerHTML = "Done";
\r
674 iwfHideGentlyDelay(_iwfGetBusyBar(false), 15, 500);
\r
677 // use default busy implementation...
\r
678 window.status = 'done.';
\r
681 // they didn't define an end function,
\r
682 // but they did define a start one, so do nothing.
\r
686 if (window.iwfOnRequestProgress){
\r
687 iwfOnRequestProgress(_iwfPendingRequests, _iwfTotalRequests);
\r
688 } else if (!window.iwfOnRequestStart) {
\r
689 if (_iwfShowGuiProgress) {
\r
690 // use gui busy implementation
\r
691 var pct = (1 - (_iwfPendingRequests/_iwfTotalRequests)) * 100;
\r
695 var bar = _iwfGetBusyBar(true);
\r
697 iwfWidth(bar, pct);
\r
698 bar.innerHTML = "loading " + iwfIntFormat(pct) + "%";
\r
701 // use default busy implementation...
\r
702 window.status = 'Remaining: ' + _iwfPendingRequests;
\r
705 // they didn't define an end function,
\r
706 // but they did define a start one, so do nothing.
\r
711 function _iwfOnRequestError(code, msg, text){
\r
712 iwfLog("Error " + code + ": " + msg + ":\n\n" + text);
\r
713 if (window.iwfOnRequestError){
\r
714 iwfOnRequestError(code, msg, text);
\r
716 alert("Error " + code + ": " + msg + ":\n\n" + text);
\r
720 // -----------------------------------
\r
721 // End: AJAX Request and Response
\r
722 // -----------------------------------
\r