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
279 if (iwfAttribute(el, 'checked')){
\r
280 val = iwfAttribute(el, 'value');
\r
285 val = iwfAttribute(el, 'value');
\r
290 val = iwfAttribute(el, 'value');
\r
295 val = iwfAttribute(el, 'value');
\r
298 iwfLog('TODO: implement <input type="file"> in _iwfGetFormData', true);
\r
299 val = iwfAttribute(el, 'value');
\r
302 iwfLog('TODO: implement <input type="image"> in _iwfGetFormData', true);
\r
303 val = iwfAttribute(el, 'value');
\r
306 iwfLog('TODO: implement <input type="reset"> in _iwfGetFormData', true);
\r
309 val = iwfAttribute(el, 'value');
\r
314 val = iwfAttribute(el, 'innerText');
\r
318 val = iwfAttribute(el, 'innerText');
\r
322 for(var j=0;j<el.options.length;j++){
\r
323 if (iwfAttribute(el.options[j], 'selected') == 'true'){
\r
325 val = iwfAttribute(el.options[j], 'value');
\r
327 val += '+' + iwfAttribute(el.options[j], 'value');
\r
334 if (output.length > 0){
\r
337 output += escape(nm) + '=' + escape(val);
\r
341 if (output.length == 0){
\r
348 function _iwfResponseReceived(doc){
\r
349 iwfLog('iframeloaded');
\r
350 var xmlDoc = new iwfXmlDoc(doc.innerHTML);
\r
353 function _iwfResponseHandler(origText, callback, tgt){
\r
354 var doc = new iwfXmlDoc(origText);
\r
355 if (!doc.response){
\r
356 // not in our default xml format.
\r
357 // just throw out the xml to the callback, if any
\r
361 iwfLog("IWF Ajax Error: No callback defined for non-standard response:\n" + origText, true);
\r
364 if (doc.response.debugging == 'true'){
\r
365 iwfLoggingEnabled = true;
\r
366 iwfLog("IWF Ajax Debugging:\nParsed response:\n\n" + doc.response.outerXml(true), true);
\r
369 for(var i=0; i< doc.response.childNodes.length; i++){
\r
370 var node = doc.response.childNodes[i];
\r
371 if (node.nodeName.indexOf("#") != 0){
\r
372 //iwfLog('node.target=' + node.target + '\ntgt=' + tgt);
\r
374 // server target is ignored if a client target exists.
\r
377 if (node.errorCode && iwfToInt(node.errorCode, true) != 0){
\r
378 // an error occurred.
\r
379 _iwfOnRequestError(node.errorCode, node.errorMessage);
\r
384 switch(node.type.toLowerCase()){
\r
387 var innerHtml = node.innerHtml();
\r
388 //iwfLog('parsed html response:\n\n' + innerHtml);
\r
389 _iwfInsertHtml(innerHtml, tgt);
\r
394 if (node.childNodes.length == 1){
\r
395 var js = node.childNodes[0];
\r
396 if (js.nodeName == '#cdata' || js.nodeName == '#comment'){
\r
398 var code = js.getText();
\r
403 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
412 iwfLog("IWF Debug: <action> type identified as 'debug'.\nXml received for current action:\n\n" + node.outerXml(), true);
\r
415 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
425 function _iwfInsertHtml(html, parentNodeId){
\r
427 parentNodeId = 'iwfContent';
\r
428 iwfLog("IWF Ajax Warning: <action> with a type of 'html' does not have its target attribute specified, so using the default of 'iwfContent'.");
\r
432 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
434 var el = iwfGetById(parentNodeId);
\r
436 if (parentNodeId == 'body'){
\r
437 el = iwfGetByTagName('body');
\r
438 if (!el || el.length == 0){
\r
439 iwfLog("IWF Ajax Error: Could not locate the tag named 'body'", true);
\r
445 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
450 //iwfLog(iwfElementToString(el));
\r
453 // trying to shove a <form> node inside another <form> node is a bad thing.
\r
454 // make sure we don't do that here.
\r
456 var match = re.exec(html);
\r
457 if (match && document.forms.length > 0){
\r
458 // our html to inject contains a form node.
\r
459 // bubble up the chain until we find a <form> node, or we have no parents
\r
461 while (elParent && elParent.tagName.toLowerCase() != 'form'){
\r
462 elParent = iwfGetParent(elParent);
\r
465 if (elParent && elParent.tagName.toLowerCase() == 'form'){
\r
466 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
471 el.innerHTML = html;
\r
474 // if there is a script element, we have to explicitly add it to the body element,
\r
475 // as IE and firefox just don't like it when you try to add it as part of the innerHTML
\r
476 // property. Go figure.
\r
479 // don't stomp on any existing scripts...
\r
480 while (iwfGetById('iwfScript' + i)){
\r
484 var scriptStart = html.indexOf("<script");
\r
485 while(scriptStart > -1){
\r
486 scriptStart = html.indexOf(">", scriptStart) + 1;
\r
488 // copy contents of script into a default holder
\r
489 var scriptEnd = html.indexOf("</script>", scriptStart);
\r
490 var scriptHtml = html.substr(scriptStart, scriptEnd - scriptStart);
\r
492 var re = /^\s*<!--/;
\r
493 var match = re.exec(scriptHtml);
\r
495 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
498 // this code is the worst hack in this entire framework.
\r
499 // the code in the try portion should work anywhere -- but
\r
500 // IE barfs when you try to set the innerHTML on a script element.
\r
501 // not only that, but IE won't parse script unless a visible element
\r
502 // is contained in the innerHTML when it is set, so we need the <code>IE hack here</code>
\r
503 // and it cannot be removed.
\r
504 // I don't understand why creating a new node and setting its innerHTML causes the browsers
\r
505 // to parse and execute the script, but it does.
\r
506 // Note there is a major leak here, as we do not try to reuse existing iwfScriptXXXX elements
\r
507 // in case they are in use by other targets. To clean these up, simply call iwfCleanScripts
\r
508 // periodically. This is so app-dependent, I didn't want to try to keep track of everything
\r
509 // and possibly miss a case, causing really weird results that only show up occassionally.
\r
511 // Plus I'm getting lazy. :)
\r
515 var elScript = iwfGetOrCreateById('iwfScript' + i, 'script');
\r
516 elScript.type = 'text/javascript';
\r
517 elScript.defer = 'true';
\r
518 elScript.innerHTML = scriptHtml;
\r
520 iwfAppendChild('body', elScript);
\r
523 iwfLog("IE Hack for injecting script tag...", true);
\r
525 // 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
526 // My guess is the visible element causes the page to at least partially re-render, which in turn kicks off any script parsing
\r
528 var elDiv = iwfGetOrCreateById('iwfScript' + i, '<div style="display:none"></div>', 'body');
\r
529 elDiv.innerHTML = '<code>IE hack here</code><script id="iwfScript' + i + '" defer="true">' + scriptHtml + '</script' + '>';
\r
534 scriptStart = html.indexOf("<script", scriptEnd+8);
\r
539 function iwfCleanScripts(){
\r
541 while(iwfRemoveNode('iwfScript' + (i++)));
\r
547 var _iwfTotalRequests = 0;
\r
548 var _iwfPendingRequests = 0;
\r
549 var _iwfRequestTicker = null;
\r
550 var _iwfRequestTickCount = 0;
\r
551 var _iwfRequestTickDuration = 100;
\r
554 // TODO: make the styles applied be configurable variables at the top of this file.
\r
555 function _iwfGetBusyBar(inner){
\r
556 var b = iwfGetById('iwfBusy');
\r
558 b = iwfGetOrCreateById('iwfBusy', 'div', 'body');
\r
560 b.style.position = 'absolute';
\r
561 b.style.border = '1px solid black';
\r
562 b.style.backgroundColor = '#efefef';
\r
563 b.style.textAlign = 'center';
\r
567 iwfZIndex(b, 9999);
\r
572 // iwfX(b, iwfClientWidth() - iwfWidth(b)-5);
\r
573 // iwfY(b, iwfClientHeight() - iwfHeight(b)-5);
\r
580 var bb = iwfGetById('iwfBusyBar');
\r
582 bb = iwfGetOrCreateById('iwfBusyBar', 'div', b);
\r
583 bb.style.backgroundColor = 'navy';
\r
584 bb.style.color = 'white';
\r
585 bb.style.textAlign = 'center';
\r
600 function _iwfOnRequestStart(){
\r
601 _iwfPendingRequests++;
\r
602 _iwfTotalRequests++;
\r
604 _iwfRequestTickDuration = 100;
\r
606 if (!_iwfRequestTicker){
\r
607 _iwfRequestTickCount = 0;
\r
608 if (window.iwfOnRequestStart){
\r
609 _iwfRequestTickDuration = iwfOnRequestStart();
\r
610 } else if (_iwfShowGuiProgress) {
\r
611 // use gui busy implementation
\r
612 var bb = _iwfGetBusyBar(true);
\r
614 bb.innerHTML = '0%';
\r
615 iwfShow(_iwfGetBusyBar(false));
\r
617 // use default busy implementation...
\r
618 window.status = 'busy.';
\r
620 if (!_iwfRequestTickDuration){
\r
621 _iwfRequestTickDuration = 100;
\r
623 _iwfRequestTicker = setInterval(_iwfOnRequestTick, _iwfRequestTickDuration);
\r
627 function _iwfOnRequestTick(){
\r
628 _iwfRequestTickCount++;
\r
629 if (window.iwfOnRequestTick){
\r
630 iwfOnRequestTick(_iwfRequestTickCount, _iwfRequestTickDuration, _iwfPendingRequests);
\r
631 } else if (!window.iwfOnRequestStart) {
\r
632 if (_iwfShowGuiProgress) {
\r
633 // use gui busy implementation
\r
634 var bar = _iwfGetBusyBar(true);
\r
636 var w = iwfWidth(bar) + 1;
\r
641 bar.innerHTML = "loading " + iwfIntFormat(w) + "%";
\r
644 // use default busy implementation...
\r
645 window.status = 'busy...............................................'.substr(0, (_iwfRequestTickCount % 45) + 5);
\r
648 // they didn't define a tick function,
\r
649 // but they did define a start one, so do nothing.
\r
653 function _iwfOnRequestEnd(){
\r
654 _iwfPendingRequests--;
\r
655 if (_iwfPendingRequests < 1){
\r
656 _iwfPendingRequests = 0;
\r
657 _iwfTotalRequests = 0;
\r
658 clearInterval(_iwfRequestTicker);
\r
659 _iwfRequestTicker = null;
\r
660 if (window.iwfOnRequestEnd){
\r
662 } else if (!window.iwfOnRequestStart) {
\r
663 if (_iwfShowGuiProgress) {
\r
664 // use gui busy implementation
\r
665 var bar = _iwfGetBusyBar(true);
\r
667 iwfWidth(bar, 100);
\r
668 bar.innerHTML = "Done";
\r
669 iwfHideGentlyDelay(_iwfGetBusyBar(false), 15, 500);
\r
672 // use default busy implementation...
\r
673 window.status = 'done.';
\r
676 // they didn't define an end function,
\r
677 // but they did define a start one, so do nothing.
\r
681 if (window.iwfOnRequestProgress){
\r
682 iwfOnRequestProgress(_iwfPendingRequests, _iwfTotalRequests);
\r
683 } else if (!window.iwfOnRequestStart) {
\r
684 if (_iwfShowGuiProgress) {
\r
685 // use gui busy implementation
\r
686 var pct = (1 - (_iwfPendingRequests/_iwfTotalRequests)) * 100;
\r
690 var bar = _iwfGetBusyBar(true);
\r
692 iwfWidth(bar, pct);
\r
693 bar.innerHTML = "loading " + iwfIntFormat(pct) + "%";
\r
696 // use default busy implementation...
\r
697 window.status = 'Remaining: ' + _iwfRequestsPending;
\r
700 // they didn't define an end function,
\r
701 // but they did define a start one, so do nothing.
\r
706 function _iwfOnRequestError(code, msg, text){
\r
707 iwfLog("Error " + code + ": " + msg + ":\n\n" + text);
\r
708 if (window.iwfOnRequestError){
\r
709 iwfOnRequestError(code, msg, text);
\r
711 alert("Error " + code + ": " + msg + ":\n\n" + text);
\r
715 // -----------------------------------
\r
716 // End: AJAX Request and Response
\r
717 // -----------------------------------
\r