859 lines
32 KiB
JavaScript
859 lines
32 KiB
JavaScript
/*
|
|
* =================================================================
|
|
* 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.objects.workspace -->
|
|
<div id="treecats">
|
|
<!-- this.objects.selection -->
|
|
<div class="treecats-selection/treecats-selection-summary treecats-selection-single/treecats-selection-multiple">
|
|
<!-- this.objects.selectionList -->
|
|
<ul>
|
|
<li class="treecats-selection-current"><input type="hidden" name="[this.config.inputName]" value="<id>" /><span>Full/Category</span><a class="treecats-selection-change">[this.lang.change]</a><a class="treecats-selection-remove">[this.lang.remove]</a><a class="treecats-selection-done">[this.lang.done]<a/><a class="treecats-selection-cancel">[this.lang.cancel]</a></li>
|
|
...
|
|
</ul>
|
|
<!-- this.objects.selectionListAdd -->
|
|
<a class="treecats-selection-add">[this.lang.add]</a>
|
|
</div>
|
|
|
|
<!-- this.objects.tree -->
|
|
<div class="treecats-tree">
|
|
<div id="tc-c[id]" class="treecats-category">
|
|
<div class="treecats-category-info treecats-selected">
|
|
<a href="javascript:tc.navigate([id])"><img /></a> <span title="Full/Category"><a href="javascript:tc.select(<id>)">Category Name</a></span>
|
|
</div>
|
|
<div class="treecats-children">
|
|
<div id="tc-c[id]" class="treecats-category">...</div>
|
|
...
|
|
<ul class="treecats-links">
|
|
<li class="treecats-selected"><a href="javascript:tc.selectLink([id])" title="[URL]">Link Title</a></li>
|
|
...
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
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);
|
|
}
|