/* * ================================================================= * Gossamer Links - enhanced directory management system * * Website : http://gossamer-threads.com/ * Support : http://gossamer-threads.com/scripts/support/ * Revision : $Id: treecats.js,v 1.29 2007/11/15 01:20:18 brewt Exp $ * * Copyright (c) 2006 Gossamer Threads Inc. All Rights Reserved. * Redistribution in part or in whole strictly prohibited. Please * see LICENSE file for full details. * ================================================================= */ /*
[this.lang.add]
TODO where to append the div's to (for absolute positioning to work) scroll to the selection (perhaps this should be added to _resize) */ function treecats(config, lang) { this.config = { // The method to use to browse categories. // "normal" // "collapsed" - when viewing a category, collapse all categories on the same level browseMode : 'normal', // This controls whether or not multiple categories may be selected or if links // are to be selected. It may be either: "single", "multiple", or "link". selectionMode : 'single', // When selectionMode is 'multiple', this is the maximum number of categories // to allow. Set this to 0 for an unlimited number of categories. multipleMax : 0, // Whether or not a selection is required. If a selection is not required, a // root entry will be displayed (using lang.rootText), allowing the user to // unselect the current selection. Note that this option does not force the // user to make a selection. This option is only valid if selectionMode is set // to 'single'. selectionRequired : true, // Whether or not selecting a category will expand its children. selectionExpands : true, // The URL to treecats.cgi. cgiURL : '', // Extra query string to add to the url. cgiQueryString : '', // The URL to the expand.gif, collapse.gif, root.gif and loading.gif images. imageURL : '', // The name of the treecats object. objName : 'tc', // The id of the area that treecats will use. workspace : 'treecats', // The maximum height (in pixels) of the tree area before a scrollbar is // enabled. Set this to 0 for no maximum height. maxHeight : 500, // The form input name. inputName : 'CatLinks.CategoryID' }; this.lang = { noSelection : 'No category selected', noSelectionLink : 'No link selected', rootText : 'All Categories', expand : 'Expand', collapse : 'Collapse', change : '[Change]', remove : '[Remove]', done : '[Done]', cancel : '[Cancel]', add : '[Add]', loading : 'Loading', duplicate : 'You have already selected this category.' }; if (config) { for (var key in config) this.config[key] = config[key]; } if (lang) { for (var key in lang) this.lang[key] = lang[key]; } this.objects = { workspace : null, selection : null, selectionList : null, selectionListAdd : null, tree : null }; // A backup of the selection to allow cancel this.selectionBackup = null; // The ID of the selection item currently being used this.currentSelectionItem = 0; // The ID of the category/link that is currently selected this.currentSelection = null; // The links (object) of the previously selected category this.links = null; // Mapping of link ID to category ID this.linkToCategory = {}; // Constants for _traverse() this.SINGLE = 0; this.DOWN = 1; this.UP = 2; // Constants for navigate() this.AUTO = 0; this.EXPAND = 1; this.COLLAPSE = 2; } treecats.prototype.load = function () { if (this.config.selectionMode != 'single') this.config.selectionRequired = true; // Accept pre-selected categories to be either inputs already on the page, or // as arguments to load(). If you use inputs already on the page, then you // must call load() after all the inputs have been loaded. var fetch = []; var inputs = document.getElementsByTagName('input'); for (var i = 0; i < inputs.length; i++) { if (inputs[i].name == this.config.inputName) { if (inputs[i].value > 0) fetch.push(inputs[i].value); inputs[i].parentNode.removeChild(inputs[i]); i--; } } for (var i = 0; i < arguments.length; i++) { if (arguments[i] > 0) fetch.push(arguments[i]); } this.objects.workspace = document.getElementById(this.config.workspace); this.objects.selection = document.createElement('div'); this.objects.selection.className = 'treecats-selection-summary ' + (this.config.selectionMode == 'multiple' ? 'treecats-selection-multiple' : 'treecats-selection-single'); this.objects.workspace.appendChild(this.objects.selection); this.objects.selectionList = document.createElement('ul'); this.objects.selection.appendChild(this.objects.selectionList); if (this.config.selectionMode == 'multiple') { this.objects.selectionListAdd = document.createElement('a'); this.objects.selectionListAdd.href = 'javascript:' + this.config.objName + '.add()'; this.objects.selectionListAdd.className = 'treecats-selection-add'; this.objects.selectionListAdd.style.display = 'none'; this.objects.selectionListAdd.appendChild(document.createTextNode(this.lang.add)); this.objects.selection.appendChild(this.objects.selectionListAdd); } this.objects.tree = document.createElement('div'); this.objects.tree.className = 'treecats-tree'; this.objects.tree.style.display = 'none'; this.objects.workspace.appendChild(this.objects.tree); if (this.config.cgiURL) this.config.cgiURL = this.config.cgiURL.replace(/^https?:\/\/[^\/]+\//, '/'); if (fetch.length == 0) { this._createSelection(); fetch = [0]; } var type = this.config.selectionMode == 'link' ? 'lid=' : 'cid='; this._asyncReq(this._treeHandler, this.config.cgiURL + '/treecats.cgi', type + fetch.join('&' + type) + (this.config.cgiQueryString ? '&' + this.config.cgiQueryString : '')); } // Toggle the summary mode display. Pass in a true value for the cancel // argument if you wish to revert to the original selection. treecats.prototype.toggle = function (id, cancel) { if (!id) return; var selected = document.getElementById(this._sid(id)); // Show the tree if (this.objects.tree.style.display == 'none') { this.currentSelectionItem = id; // Save the current selection (so the user can cancel) this.selectionBackup = { title : selected.childNodes[1].firstChild.nodeValue, value : selected.firstChild.value }; for (var i = 0; i < this.objects.selectionList.childNodes.length; i++) { var curr = this.objects.selectionList.childNodes[i]; // Show the Done/Cancel links on the current selection item if (curr == selected) curr.childNodes[4].style.display = curr.childNodes[5].style.display = ''; // Hide the Change/Remove links curr.childNodes[2].style.display = curr.childNodes[3].style.display = 'none'; } this.collapseAll(); var sid = selected.firstChild.value || 0; if (this.config.selectionMode != 'link') { this.expandTo(sid); this.select(sid); } else { this.expandTo(this.linkToCategory[sid], true); this.selectLink(sid); } selected.childNodes[1].className = 'treecats-selection-current'; this.objects.tree.style.display = ''; this.objects.selection.className = this.objects.selection.className.replace(/treecats-selection-summary /, 'treecats-selection '); this._resize(); } else { // Check to see that the user's selection isn't a duplicate if (!cancel && this.config.selectionMode == 'multiple' && selected.firstChild.value > 0) { for (var i = 0; i < this.objects.selectionList.childNodes.length; i++) { var curr = this.objects.selectionList.childNodes[i]; if (curr == selected) continue; if (curr.firstChild.value == selected.firstChild.value) { alert(this.lang.duplicate); return; } } } // Restore the previous selection if (cancel) this._updateSelection(this.selectionBackup.title, this.selectionBackup.value); for (var i = 0; i < this.objects.selectionList.childNodes.length; i++) { var curr = this.objects.selectionList.childNodes[i]; // Hide the Done/Cancel links if (curr == selected) curr.childNodes[4].style.display = curr.childNodes[5].style.display = 'none'; // Show the Change/Remove links curr.childNodes[2].style.display = ''; if (this.objects.selectionList.childNodes.length > 1) curr.childNodes[3].style.display = ''; } selected.childNodes[1].className = ''; this.objects.tree.style.display = 'none'; this.objects.selection.className = this.objects.selection.className.replace(/treecats-selection /, 'treecats-selection-summary '); } this._updateListAdd(); } // Select a Category treecats.prototype.select = function (id) { if (this.config.selectionMode == 'link') return; // Unselect the previously selected category var already_selected = this.currentSelection == id; if (!already_selected) { var prevCat = document.getElementById(this._id(this.currentSelection)); if (prevCat) prevCat.firstChild.className = prevCat.firstChild.className.replace(/ ?treecats-selected/, ''); this.currentSelection = id; } var cat = document.getElementById(this._id(id)); if (!cat) return; if (!already_selected) { this._updateSelection(id > 0 ? cat.firstChild.lastChild.title : this.lang.rootText, id); // Set the selected class on the selection cat.firstChild.className += ' treecats-selected'; } // Expand the category's children on selection if (this.config.selectionExpands && this.objects.tree.style.display != 'none' && id != 0 && (already_selected || cat.childNodes[1].style.display == 'none')) this.navigate(id); } // Select a link treecats.prototype.selectLink = function (id) { if (this.config.selectionMode != 'link') return; var link = document.getElementById(this._lid(id)); if (!link) return; var already_selected = this.currentSelection == id; if (already_selected) return; // The link may be in multiple categories var ext = ''; var count = 0; var unselect; while (unselect = document.getElementById(this._lid(this.currentSelection + ext))) { unselect.className = unselect.className.replace(/ ?treecats-selected/, ''); count++; ext = '-' + count; } this.currentSelection = id; this._updateSelection(link.firstChild.firstChild.nodeValue, id); ext = ''; count = 0; var select; while (select = document.getElementById(this._lid(id + ext))) { select.className += ' treecats-selected'; count++; ext = '-' + count; } } // Expand or collapse a category. // state: 0 = auto (this.AUTO), 1 = force expand (this.EXPAND), 2 = force collapse (this.COLLAPSE) treecats.prototype.navigate = function (id, state) { var cat = document.getElementById(this._id(id)); if (!cat) return; var me = this; // Collapse categories on the same level as the current category var collapseSiblings = function () { if (me.config.browseMode != 'collapsed') return; var siblings = cat.parentNode.childNodes; for (var i = 0; i < siblings.length; i++) { if (siblings[i] == cat || !siblings[i].className.match(/treecats-category/)) continue; if (siblings[i].childNodes[1].style.display != 'none') me.navigate(me._getId(siblings[i])); } } // Can't expand a category if it doesn't have children, but if browseMode is // collapsed, then siblings may need to be collapsed if (cat.firstChild.firstChild.tagName.toLowerCase() != 'a') { collapseSiblings(); return; } // Collapse/Expand a category var toggle = function (c) { var collapse = c.childNodes[1].style.display == 'none'; if (state == me.EXPAND) collapse = 1; else if (state == me.COLLAPSE) collapse = 0; c.firstChild.firstChild.firstChild.src = me.config.imageURL + (collapse ? '/collapse.gif' : '/expand.gif'); c.firstChild.firstChild.firstChild.alt = collapse ? '[-]' : '[+]'; c.firstChild.firstChild.firstChild.title = collapse ? me.lang.collapse : me.lang.expand; c.childNodes[1].style.display = collapse ? '' : 'none'; }; var toggleChildren = function () { // Collapse any open categories on the same level collapseSiblings(); toggle(cat); }; var toggleLinks = function () { if (me.config.selectionMode != 'link') return; var links = cat.childNodes[1].lastChild; if (!links) return; // Hide the previous category's links if a different category has been selected if (me.links && (me.links != links || links.className != 'treecats-links')) { me.links.style.display = 'none'; // There are only links in this category, so the category needs to be collapsed if (me.links.parentNode.childNodes.length == 1 && me.links.parentNode.style.display != 'none') toggle(me.links.parentNode.parentNode); } if (links.className == 'treecats-links') { me.links = links; links.style.display = ''; } else me.links = null; }; // The category has already been fetched if (cat.childNodes[1].hasChildNodes() || state == this.COLLAPSE) { // In link mode, only one category's links are shown at a time, so selecting a // category that is already expanded shouldn't always collapse it. // Also, in link mode, a category that has no links should collapse immediately. if (this.config.selectionMode != 'link' || cat.childNodes[1].style.display == 'none' || state == this.COLLAPSE || (this.links && this.links == cat.childNodes[1].lastChild) || cat.childNodes[1].lastChild.className != 'treecats-links') toggleChildren(); toggleLinks(); this._resize(); } else { cat.firstChild.firstChild.firstChild.src = this.config.imageURL + '/loading.gif'; cat.firstChild.firstChild.firstChild.title = this.lang.loading; this._asyncReq(function (me, req) { toggleChildren(); me._treeHandler(me, req); toggleLinks(); }, this.config.cgiURL + '/treecats.cgi?id=' + id + (this.config.selectionMode == 'link' ? ';links=1' : '') + (this.config.cgiQueryString ? ';' + this.config.cgiQueryString : '')); } } // Traverse the category tree. // dir: 0 = just that one (this.SINGLE), 1 = down (this.DOWN), 2 = up (this.UP) treecats.prototype._traverse = function (id, func, dir) { // When selectionRequired is true, there is no category with an ID of 0 if (id == 0 && this.config.selectionRequired) { if (dir == this.UP) return; for (var i = 0; i < this.objects.tree.childNodes.length; i++) this._traverse(this._getId(this.objects.tree.childNodes[i]), func, dir); return; } var cat = document.getElementById(this._id(id)); if (!cat) return; func(cat, id); // Traverse down the tree if (dir == 1) { for (var i = 0; i < cat.lastChild.childNodes.length; i++) { if (cat.lastChild.childNodes[i].className.match(/treecats-category/)) this._traverse(this._getId(cat.lastChild.childNodes[i]), func, dir); } } // Traverse up the tree else if (dir == 2) { if (cat.parentNode.className.match(/treecats-children/)) this._traverse(this._getId(cat.parentNode.parentNode), func, dir); } } // Expand categories from the category up to the root. Pass in true for // inclusive if you wish to expand the category itself. treecats.prototype.expandTo = function (id, inclusive) { if (id == 0 || id == undefined) return; var start; if (inclusive) start = id; else { var cat = document.getElementById(this._id(id)); if (!cat || !cat.parentNode.className.match(/treecats-children/)) return; start = this._getId(cat.parentNode.parentNode); } // Expand the categories from the category's parent and up var me = this; this._traverse(start, function (cat, id) { me.navigate(id, me.EXPAND); }, this.UP); // Since we traverse up the tree, the side effect is that in link mode, the // root node ends up having its links shown. if (this.config.selectionMode == 'link') this.navigate(id, this.EXPAND); } // Collapse all categories treecats.prototype.collapseAll = function () { var me = this; this._traverse(0, function (cat, id) { me.navigate(id, me.COLLAPSE); }, this.DOWN); } // Add a new category selection item treecats.prototype.add = function () { if (this.config.selectionMode != 'multiple') return; var sid = this._createSelection(); // Automatically go into selection mode this.toggle(sid); } // Remove a category selection item that has the supplied id treecats.prototype.remove = function (id) { // Don't allow the removal of the last item if (this.objects.selectionList.childNodes.length <= 1) return; var selection = document.getElementById(this._sid(id)); if (!selection) return; this.objects.selectionList.removeChild(selection); // Hide the Remove link if (this.objects.selectionList.childNodes.length == 1) this.objects.selectionList.firstChild.childNodes[3].style.display = 'none'; this._updateListAdd(); } // Create a new category selection item. If no title is specified, then // this.lang.noSelection[Link] will be used. treecats.prototype._createSelection = function (title, value) { if (!title) title = this.config.selectionRequired ? (this.config.selectionMode != 'link' ? this.lang.noSelection : this.lang.noSelectionLink) : this.lang.rootText; var selection = document.createElement('li'); // Find an unused selection item ID var sid = 1; while (document.getElementById(this._sid(sid))) sid++; selection.id = this._sid(sid); var input = document.createElement('input'); input.type = 'hidden'; input.name = this.config.inputName; input.value = value > 0 ? value : ''; selection.appendChild(input); var span = document.createElement('span'); span.appendChild(document.createTextNode(title)); selection.appendChild(span); var change = document.createElement('a'); change.href = 'javascript:' + this.config.objName + '.toggle(' + sid + ')'; change.className = 'treecats-selection-change'; change.appendChild(document.createTextNode(this.lang.change)); selection.appendChild(change); var remove = document.createElement('a'); remove.href = 'javascript:' + this.config.objName + '.remove(' + sid + ')'; remove.className = 'treecats-selection-remove'; if (this.objects.selectionList.childNodes.length == 0) remove.style.display = 'none'; remove.appendChild(document.createTextNode(this.lang.remove)); selection.appendChild(remove); var done = document.createElement('a'); done.href = 'javascript:' + this.config.objName + '.toggle(' + sid + ')'; done.className = 'treecats-selection-done'; done.style.display = 'none'; done.appendChild(document.createTextNode(this.lang.done)); selection.appendChild(done); var cancel = document.createElement('a'); cancel.href = 'javascript:' + this.config.objName + '.toggle(' + sid + ',1)'; cancel.className = 'treecats-selection-cancel'; cancel.style.display = 'none'; cancel.appendChild(document.createTextNode(this.lang.cancel)); selection.appendChild(cancel); this.objects.selectionList.appendChild(selection); // Show the Remove link on all selections if (this.objects.selectionList.childNodes.length > 1) { for (var i = 0; i < this.objects.selectionList.childNodes.length; i++) this.objects.selectionList.childNodes[i].childNodes[3].style.display = ''; } return sid; } // Update the current category selection. If no selectionItem is selected, a // new selectionItem will be created. treecats.prototype._updateSelection = function (title, value) { if (!title) title = this.config.selectionRequired ? (this.config.selectionMode != 'link' ? this.lang.noSelection : this.lang.noSelectionLink) : this.lang.rootText; // When pre-selecting categories/links (this.currentSelectionItem == 0), the // selection items need to be created. if (this.currentSelectionItem > 0) var selected = document.getElementById(this._sid(this.currentSelectionItem)); else var selected = document.getElementById(this._sid(this._createSelection())); selected.firstChild.value = value > 0 ? value : ''; selected.childNodes[1].replaceChild(document.createTextNode(title), selected.childNodes[1].firstChild); } treecats.prototype._treeHandler = function (me, req) { var cats = req.responseXML.getElementsByTagName('category'); var father; var children; var error = req.responseXML.getElementsByTagName('error'); if (error.length) { alert('treecats error: ' + error[0].firstChild.nodeValue); return; } // Create a special root entry if needed var root = document.getElementById(me._id(0)); if (!me.config.selectionRequired && !root) { var xmldoc = cats[0].ownerDocument; var root = xmldoc.createElement('category'); root.setAttribute('id', 0); root.setAttribute('fatherid', -1); root.setAttribute('children', -1); root.setAttribute('selected', 0); root.setAttribute('links', 0); var name = xmldoc.createElement('name'); name.appendChild(xmldoc.createTextNode(me.lang.rootText)); root.appendChild(name); var fname = xmldoc.createElement('fullname'); fname.appendChild(xmldoc.createTextNode('')); root.appendChild(fname); cats[0].parentNode.insertBefore(root, cats[0]); // IE doesn't update the cats array like other browsers do, so refetch results cats = cats[0].parentNode.getElementsByTagName('category'); } for (var i = 0; i < cats.length; i++) { var id = cats[i].getAttribute('id'); var fid = cats[i].getAttribute('fatherid'); var childcount = parseInt(cats[i].getAttribute('children')) || 0; var linkcount = parseInt(cats[i].getAttribute('links')) || 0; var selected = cats[i].getAttribute('selected') > 0 ? true : false; var expandable = childcount; if (me.config.selectionMode == 'link') { expandable += linkcount; } if (document.getElementById(me._id(id))) continue; var cat = document.createElement('div'); cat.id = me._id(id); cat.className = 'treecats-category'; var cat_info = document.createElement('div'); cat_info.className = 'treecats-category-info'; cat.appendChild(cat_info); var img = document.createElement('img'); img.src = me.config.imageURL + (expandable > 0 ? '/expand.gif' : expandable < 0 ? '/root.gif' : '/blank.gif'); if (expandable > 0) { img.alt = '[+]'; img.title = me.lang.expand; var img_link = document.createElement('a'); img_link.href = 'javascript:' + me.config.objName + '.navigate(' + id + ')'; img_link.appendChild(img); cat_info.appendChild(img_link); } else cat_info.appendChild(img); var cat_span = document.createElement('span'); cat_span.title = cats[i].getElementsByTagName('fullname')[0].firstChild.nodeValue; cat_info.appendChild(cat_span); var cat_name = document.createTextNode(cats[i].getElementsByTagName('name')[0].firstChild.nodeValue); // If we're in link selectionMode, then only make the Category linkable if it // has Links or sub-categories. Also navigate() instead of select(). if ((me.config.selectionMode == 'link' && expandable) || me.config.selectionMode != 'link') { var cat_link = document.createElement('a'); cat_link.href = 'javascript:' + me.config.objName + (me.config.selectionMode != 'link' ? '.select' : '.navigate') + '(' + id + ')'; cat_link.appendChild(cat_name); cat_span.appendChild(cat_link); } else cat_span.appendChild(cat_name); var cat_child = document.createElement('div'); cat_child.className = 'treecats-children'; if (id != 0) cat_child.style.display = 'none'; cat.appendChild(cat_child); if (!father || father.id != me._id(fid)) { if ((fid == 0 && me.config.selectionRequired) || (id == 0 && !me.config.selectionRequired)) father = me.objects.tree; else father = document.getElementById(me._id(fid)); if (!father) { alert('treecats error: Father id (' + fid + ') hasn\'t been created yet!'); continue; } if ((fid == 0 && me.config.selectionRequired) || (id == 0 && !me.config.selectionRequired)) children = father; else children = father.childNodes[1]; } children.appendChild(cat); if (selected && me.config.selectionMode != 'link') me._updateSelection(cats[i].getElementsByTagName('fullname')[0].firstChild.nodeValue, id); } if (me.config.selectionMode == 'link') { var links = req.responseXML.getElementsByTagName('link'); if (links.length) { for (var i = 0; i < links.length; i++) { var linkid = links[i].getAttribute('id'); var catid = links[i].getAttribute('catid'); var selected = links[i].getAttribute('selected') > 0 ? true : false; var cat = document.getElementById(me._id(catid)); var cat_child = cat.lastChild; var ul = cat_child.lastChild; if (!ul || !ul.className.match(/treecats-links/)) { ul = document.createElement('ul'); ul.className = 'treecats-links'; ul.style.display = 'none'; cat_child.appendChild(ul); } var li = document.createElement('li'); // Give links which are in multiple categories a different ID var ext = ''; var count = 0; var dupe; while (dupe = document.getElementById(me._lid(linkid + ext))) { // Select this link if it's already selected (from another category) if (li.className != dupe.className) li.className = dupe.className; count++; ext = '-' + count; } li.id = me._lid(linkid + ext); var link = document.createElement('a'); link.href = 'javascript:' + me.config.objName + '.selectLink(' + linkid + ')'; link.title = links[i].getElementsByTagName('url')[0].firstChild.nodeValue; link.appendChild(document.createTextNode(links[i].getElementsByTagName('name')[0].firstChild.nodeValue)); li.appendChild(link); ul.appendChild(li); // Check that the link hasn't already been added if (selected && !me.linkToCategory[linkid]) me._updateSelection(links[i].getElementsByTagName('name')[0].firstChild.nodeValue, linkid); // Save where the link is so we can expand to the category later on me.linkToCategory[linkid] = catid; } } } me._updateListAdd(); me._resize(); } // Update the visibility of the Add link treecats.prototype._updateListAdd = function () { if (this.config.selectionMode != 'multiple') return; if (this.config.multipleMax > 0 && this.objects.selectionList.childNodes.length >= this.config.multipleMax) this.objects.selectionListAdd.style.display = 'none'; else this.objects.selectionListAdd.style.display = (this.objects.tree.style.display == 'none' && this.objects.selectionList.lastChild.firstChild.value > 0) ? '' : 'none'; } // Check to see if scrollbars need to be added or removed treecats.prototype._resize = function () { if (this.config.maxHeight > 0) { if (this.objects.tree.scrollHeight > this.config.maxHeight) { this.objects.tree.style.height = this.config.maxHeight + 'px'; this.objects.tree.style.overflow = 'scroll'; } else { this.objects.tree.style.height = ''; this.objects.tree.style.overflow = ''; } } } // Get the ID from an object treecats.prototype._getId = function (obj) { return obj.id.replace(/^.*?(\d+)$/, '$1'); } // Create a Category HTML ID treecats.prototype._id = function (id) { return this.config.objName + '-c' + id; } // Create a Link HTML ID treecats.prototype._lid = function (id) { return this.config.objName + '-l' + id; } // Create a Selection HTML ID treecats.prototype._sid = function (id) { return this.config.objName + '-s' + id; } // Perfom an asynchronous request treecats.prototype._asyncReq = function (handler, url, post_data) { var req; if (window.XMLHttpRequest) req = new XMLHttpRequest(); else if (window.ActiveXObject) { try { req = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) { try { req = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) {} } } if (!req) { alert('treecats error: Your browser does not support XMLHttpRequest'); return; } var me = this; req.onreadystatechange = function (e) { if (req.readyState == 4) { if (req.status == 200) handler(me, req); else alert('treecats error: A ' + req.status + " error occured while retrieving the XML data (" + url + "):\n" + req.statusText); } }; try { req.open(post_data ? "POST" : "GET", url, true); } catch (e) { alert('treecats error: ' + e + ".\nPerhaps cgiURL does not match the current URL."); return; } if (post_data) req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); req.send(post_data); }