/* Copyright (c) 2005 James Baicoianu This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* Simple AJAX library - this works by sending requests to the server, which returns results as XML-encapsulated XHTML, which is then placed in the target div automatically. Typical response: Data updated successfully ]]> Any number of blocks can be returned in response to a single request, thus giving the backend direct control over every named element on the webpage. Supports full command queueing - multiple calls to Queue() can be made, followed by a single call to Go() to retrieve them all at once (currently only using a single XMLHttpRequest object - if needed, could be extended to thread multiple XMLHttpRequest objects for parallelized data retrieval */ elation.extend("ajax", new function() { this.Queue = function (obj) { // if args is object, convert to string. this might not be the best place to put this. if (elation.utils.arrayget(obj, 'args') && typeof obj.args == 'object') obj.args = elation.utils.encodeURLParams(obj.args); if (obj.constructor.toString().indexOf("Array") != -1) { for (var i = 0; i < obj.length; i++) { if (!obj[i].method) obj[i].method = "GET"; this.urlqueue.push(obj[i]); } } else { if (!obj.method) obj.method = "GET"; this.urlqueue.push(obj); } if (this.xmlhttpReady()) this.Go(); } this.Get = function (url, params, args) { // FIXME - handle generating url using params array var req = this.parseURL(url); this.ProcessRequest(req, args); } this.Post = function (form, params, args) { // FIXME - handle merging params array into form request var req = this.parseForm(form); this.ProcessRequest(req, args); } this.Inject = function(targetid, url, params, args) { if (!args) args = {}; args.callback = function(html) { var destination = document.getElementById(targetid); if (destination) destination.innerHTML = html; } this.Get(url, params, args); } this.ProcessRequest = function (req, args) { if (typeof args != 'undefined') { req.history = args.history || false; if (args.callback) req.callback = args.callback; if (args.failurecallback) req.failurecallback = args.failurecallback; if (args.timeout) req.timeout = args.timeout; if (args.timeoutcallback) req.timeoutcallback = args.timeoutcallback; } this.Queue(req); } this.Go = function() { if (this.urlqueue.length > 0) { obj = this.urlqueue.shift(); //this._get(url); if (!this._go(obj)) this.urlqueue.unshift(obj); } } this.parseURL = function(turl) { var ret = new Object(); ret.method = "GET"; var url = new String(turl); // JavaScript passes a reference to the A HREF, not an actual string if (url.indexOf("?") > 0) { ret.url = url.substr(0, url.indexOf("?")); ret.args = url.substr(url.indexOf("?") + 1); } else { ret.url = url; ret.args = ""; } return ret; } this.parseForm = function(form) { var ret = new Object(); ret.method = (form.getAttribute("method") ? form.getAttribute("method").toUpperCase() : "GET"); ret.url = form.getAttribute("action"); ret.args = ""; for (var i = 0; i < form.elements.length; i++) { element = form.elements[i]; var name = new String(element.name); // for some reason, element.name isn't a String by default if (name.length > 0 && name != "undefined" && element.value != "undefined" && !element.disabled) { if (element.type == "checkbox") { ret.args += "&" + escape(name) + "=" + (element.checked ? (element.getAttribute("value") ? escape(element.value) : 1) : 0); } else if (element.type == "radio") { if (element.checked) { ret.args += "&" + escape(name) + "=" + escape(element.value); } } else { ret.args += "&" + escape(name) + "=" + escape(element.value).replace(/\+/g, "%2B"); } } } return ret; } this.xmlhttpReady = function() { if (this.xmlhttp.readyState > 0 && this.xmlhttp.readyState < 4) { return false; } return true; } this.processResponse = function(data, nobj) { // If there's no obj variable, this isn't being called from a closure so use the function argument instead if (typeof obj == 'undefined') { obj = nobj; } // If this isn't an ajaxlib response, just return the raw data if (!data.responses) { return data; } var responses = data.responses; if ( (typeof elation.search != 'undefined' && typeof elation.search.backbutton != 'undefined') && (typeof search != 'undefined' && search.urlhash) && (typeof obj != 'undefined' && obj.url == '' && !elation.utils.isTrue(obj.ignore)) ) { elation.search.backbutton.add(responses, obj); } // Used to keep track of registered dependencies, etc. while all responses are processed var common = { inlinescripts: [], data: {}, dependencies: {} }; for (var i = 0; i < responses.length; i++) { var type = responses[i].type || 'xhtml'; if (typeof this.responsehandlers[type] == 'function') { this.responsehandlers[type](responses[i], common); } else { console.log('No handler for type ' + type); } } // Process all CSS and JS dependencies into URLs var cssparms = '', javascriptparms = ''; for (var key in common.dependencies.css) { if (common.dependencies.css.hasOwnProperty(key)) { if (common.dependencies.css[key].length > 0) { cssparms += key + '=' + common.dependencies.css[key].join('+') + '&'; } } } for (var key in common.dependencies.javascript) { if (common.dependencies.javascript.hasOwnProperty(key)) { if (common.dependencies.javascript[key].length > 0) { javascriptparms += key + '=' + common.dependencies.javascript[key].join('+') + '&'; } } } var batch = new elation.file.batch(); if (cssparms.length > 0) batch.add('/css/main?'+cssparms.substr(0,cssparms.length-1),'css'); if (javascriptparms.length > 0) batch.add('/scripts/main?'+javascriptparms.substr(0,javascriptparms.length-1),null,true); common.inlinescripts.push("elation.component.init();"); // Execute all inline scripts var execute_scripts = function() { if (common.inlinescripts.length > 0) { var script_text = ''; for (var i = 0; i < common.inlinescripts.length; i++) { if (!common.inlinescripts[i] || typeof common.inlinescripts[i] == 'undefined') continue; else script_text += common.inlinescripts[i] + '\n'; } try { eval(script_text); } catch(e) { batch.callback(script_text); } } } // FIXME - this had a delay of 1ms when type='data' and name='infobox.data' was passed, I'm sure there was a reason but it doesn't work with the way this is done now... execute_scripts(); // no timer makes priceslider happy! no ugly delay. // If caller passed in a callback, execute it if (typeof obj != 'undefined' && obj.callback) { try { elation.ajax.executeCallback(obj.callback, common.data); } catch(e) { batch.callback(function() { elation.ajax.executeCallback(obj.callback, common.data); }); } } } var register_inline_scripts = function(common, element) { var scripts = element.getElementsByTagName("SCRIPT"); if (scripts.length > 0) { for (var i = 0; i < scripts.length; i++) { if (typeof scripts[i].text == 'string') { common.inlinescripts.push(scripts[i].text); } else if (scripts[i].src) { console.log('elation.ajax: found inline script with src parameter'); } } } } this.responsehandlers = { 'infobox': function(response, common) { var content = response['_content'], name = response['name'], infobox; if (name && content) { infobox = elation.ui.infobox.get(name); if (infobox) { infobox.ajax_continue(content); register_inline_scripts(common, infobox.elements.container); } } }, 'notify': function(response, common) { var content = response['_content'], name = response['name'], infobox; elation.ui.notify.show(name, content); }, 'xhtml': function(response, common) { if (response['target'] && response['_content']) { var targetel = document.getElementById(response['target']); if (targetel) { if (response['append'] == 1 || response['append'] == 'true') { targetel.innerHTML += response['_content']; } else { //thefind.func.ie6_purge(targetel); if (response['target'] == 'tf_search_results_main') { response['_content'] += "
" } var infobox = elation.ui.infobox.target(targetel); if (infobox) infobox.animate_inject(response['_content'], targetel); else targetel.innerHTML = response['_content']; } register_inline_scripts(common, targetel); /* repositions infobox after ajax injection, use responsetype ["infobox"] if applicable */ if (elation.ui && elation.ui.infobox && infobox && infobox.args.reposition) { common.inlinescripts.push("elation.ui.infobox.position('"+infobox.name+"', true);"); } } } }, 'javascript': function(response, common) { if (response['_content']) { common.inlinescripts.push(response['_content']); } }, 'data': function(response, common) { if (response['name'] && response['_content']) { common.data[response['name']] = elation.JSON.parse(response['_content']); /* FIXME - this also seems like an odd place for infobox-related code (see above) */ if (response['name'] == 'infobox.content') { var text = elation.JSON.parse(response['_content']), div = document.createElement('div'); //thefind.func.ie6_purge(div); div.innerHTML = text; register_inline_scripts(common, div); /* repositions infobox after ajax injection, use responsetype ["infobox"] if applicable */ var infobox = elation.ui.infobox.getCurrent(); if (infobox && infobox.args.reposition) common.inlinescripts.push("elation.ui.infobox.position('"+infobox.name+"', true);"); } } }, 'dependency': function(response, common) { if (response['deptype'] == 'component' && response['name']) { var name = response['name'].split('.', 2); // FIXME - this won't work with "deep" components (eg. thefind.search.filters.color) if (name[0] && response['subtypes']) { var subtypes = response['subtypes'].split(','); for (var i = 0; i < subtypes.length; i++) { if (!common.dependencies[subtypes[i]]) common.dependencies[subtypes[i]] = []; if (!common.dependencies[subtypes[i]][name[0]]) common.dependencies[subtypes[i]][name[0]] = []; common.dependencies[subtypes[i]][name[0]].push(name[1]); } } } }, 'debug': function(response, common) { if (response['_content']) { var debugcontainer = document.getElementById('tf_debug_tab_logger'); if (debugcontainer) { //thefind.func.ie6_purge(debugcontainer); debugcontainer.innerHTML += response['_content']; } if (typeof tf_debugconsole != 'undefined') tf_debugconsole.scrollToBottom(); } }, 'args': function(response, common) { // FIXME: need to ask James what is being sent as responsetype == args } } this.translateXML = function(dom) { // Convert an XML object into a simple object var ret = {}; if (dom && dom.childNodes) { var tagname = dom.tagName; ret[tagname] = []; for (var i = 0; i < dom.childNodes.length; i++) { var res = dom.childNodes.item(i); if (res.nodeType == 1) { // Right now we only understand ELEMENT_NODE types var newres = {}; for (var j = 0; j < res.attributes.length; j++) { newres[res.attributes[j].nodeName] = res.attributes[j].nodeValue; } newres['_content'] = (res.firstChild ? res.firstChild.nodeValue : false); ret[tagname].push(newres); } } } return ret; } this._go = function(obj) { var docroot = this.docroot; // Need to assign these to local variables so the subfunction can access them var xmlhttp = this.xmlhttp; var processResponse = this.processResponse; var timeouttimer = false; if (obj.history) { this.setHistory(obj.args); if (this.iframe) { this.iframe.src = "/ajax-blank.htm?" + obj.args + "#" + obj.url; // We'll get back to the processing once the iframe has loaded. Bail out early here. return; } } if (!obj.cache) { obj.args = (obj.args && obj.args.length > 0 ? obj.args + "&" : "") + "_ajaxlibreqid=" + (parseInt(new Date().getTime().toString().substring(0, 10)) + parseFloat(Math.random())); } if (obj.timeout && obj.timeoutcallback) { timeouttimer = window.setTimeout(function() { obj.failurecallback = false; xmlhttp.abort(); obj.timeoutcallback(); }, obj.timeout || 5000); } readystatechange = function() { if (xmlhttp.readyState == 4) { if (timeouttimer) window.clearTimeout(timeouttimer); if (xmlhttp.status == 200) { if (xmlhttp.responseXML) { var dom = xmlhttp.responseXML.firstChild; var results = []; processResponse.call(elation.ajax, elation.ajax.translateXML(dom), obj); if (obj.callback) { elation.ajax.executeCallback(obj.callback, xmlhttp.responseText); } } else if (xmlhttp.responseText) { if (obj.callback) { elation.ajax.executeCallback(obj.callback, xmlhttp.responseText); } } } else { if (obj.failurecallback) { elation.ajax.executeCallback(obj.failurecallback); } } setTimeout('elation.ajax.Go()', 0); } } //alert('trying '+obj.method+' '+obj.url); try { if (obj.method == "POST") { xmlhttp.open(obj.method, obj.url, true); xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xmlhttp.setRequestHeader("X-Ajax", "1"); xmlhttp.onreadystatechange = readystatechange; xmlhttp.send(obj.args); } else if (obj.method == "GET") { xmlhttp.open(obj.method, obj.url + "?" + obj.args, true); //xmlhttp.setRequestHeader("X-Ajax", "1"); xmlhttp.onreadystatechange = readystatechange; xmlhttp.send(null); } else if (obj.method == "SCRIPT") { var url = (obj.url.match(/^https?:/) ? obj.url : this.host + obj.url); if (obj.args) url += '?' + elation.utils.encodeURLParams(obj.args); elation.file.get('javascript', url); } } catch (e) { if (typeof console != 'undefined') { console.log(e); } if (obj.failurecallback) { elation.ajax.executeCallback(obj.failurecallback, e); } return false; } return true; } this.setHistory = function(hash) { // FIXME - history shoult also store page, not just query string this.docroot.location.hash = hash; this.lasthash = this.docroot.location.hash; } this.checkHistory = function() { if (this.docroot.location.hash != this.lasthash) { this.processHash(this.docroot.location.hash); this.lasthash = this.docroot.location.hash; } } this.processHash = function(hash) { return false; var url = String(document.location); /* if (hash.length > 0) if (url.indexOf("#") > 0) this.Get(url.substr(0, url.indexOf("#")) + "?" + hash.substr(1)); else this.Get(url + "?" + hash.substr(1)); else this.Get(url.replace("#", "?")); */ var url = elation.utils.parseURL(document.location.href); //console.log(document.location.href, url); url.hash = ""; var hashparts = hash.split("&"); for (var i = 0; i < hashparts.length; i++) { var argparts = hashparts[i].split("="); url.args[argparts[0]] = url.args[argparts[1]]; } //console.log(elation.utils.makeURL(url)); } this.setLoader = function(target, img, text) { if (!text) text = ""; if (e = document.getElementById(target)) { //thefind.func.ie6_purge(e); e.innerHTML = '
' + text + 'Loading...
'; } } this.getHTTPObject = function() { if (!this.xmlhttp) { var xmlhttp = false; if (typeof ActiveXObject != 'undefined') { try { xmlhttp = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { try { xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); } catch (E) { xmlhttp = false; } } } if (!xmlhttp && typeof XMLHttpRequest != "undefined") { try { xmlhttp = new XMLHttpRequest(); } catch (e) { xmlhttp = false; } } this.xmlhttp = xmlhttp; } return this.xmlhttp; } this.getIFRAMEObject = function(iframeID) { /* dynamic IFRAME code original JS by Eric Costello (glish.com) for ADC http://developer.apple.com/internet/webcontent/iframe.html courtesy of http://jszen.blogspot.com/2005/03/dynamic-old-school-iframes.html */ if (!this.iframe) { //FIXME this is because james is angry... and because ie6 is reporting an ssl erro because of the iframe return; var iframe, iframeDocument; if (document.createElement) { try { var tempIFrame = document.createElement('iframe'); tempIFrame.setAttribute('id',iframeID); tempIFrame.style.border = '0px'; tempIFrame.style.width = '0px'; tempIFrame.style.height = '0px'; iframe = document.body.appendChild(tempIFrame); if (document.frames) { /* IE5 Mac only allows access to the document of the IFrame through frames collection */ iframe = document.frames[iframeID]; } } catch (ex) { /* This part is CRAZY! -- scottandrew */ /* IE5 PC does not allow dynamic creation and manipulation of an iframe object. Instead, we'll fake it up by creating our own objects. */ var iframeHTML = '\