/**
* Shortcut to access DOM element
* @param str The tag name or the ID of a DOM Element
* @return All elements found corresponding to the given tag name or ID, or undefined if none has been found
**/
function $(str)
{
if (str.length == 0)
return undefined;
if (str.charAt (0) == "#")
{
return document.getElementById(str.slice (1));
}
else
{
return document.getElementsByTagName(str);
}
}
/**
* Shortcut to access DOM element within a container
* @param str The tag name or the ID of a child Element
* @return All elements found in source object corresponding to the given tag name or ID, or undefined if none has been found
**/
HTMLElement.prototype.$ = function (str)
{
if (str.length == 0)
return undefined;
if (str.charAt (0) == "#")
{
return this.getElementById(str.slice (1));
}
else
{
return this.getElementsByTagName(str);
}
}
/**
* Counts the number of occurences of an element in an array
* @param arr A 1D-Array
* @param ele The element to look for
* @return The number of occurences of /ele/ in /arr/
**/
function arrayCount (arr, ele)
{
var n = 0;
for (var i = 0, l = arr.length; i<l; i++)
{
if (arr[i] == ele)
n++;
}
return n;
}
/**
* Creates an Object used to communicate asynchronously with Javascript (AJAX)
* @return An ActiveXObject or a XMLHttpRequest depending on the browser. May return /null/ if no object can be created.
**/
function getAjaxObject ()
{
var xhr = null;
try { xhr = new ActiveXObject('Msxml2.XMLHTTP'); }
catch (e)
{
try { xhr = new ActiveXObject('Microsoft.XMLHTTP'); }
catch (e2)
{
try { xhr = new XMLHttpRequest(); }
catch (e3) { xhr = false; }
}
}
return xhr;
}
/**
* Communicates with NCBI to retrieve content about taxonomy
* @param nameOrID Taxon ID or name
* @param target The element to display the name in
**/
function getNCBIContent (nameOrID, target)
{
if (parseInt(nameOrID) == nameOrID)
getNCBIContentByID (nameOrID, target);
else
getNCBIContentByName (nameOrID, target);
}
/**
* Communicates with NCBI to retrieve content about taxonomy
* @param id Taxon ID
* @param target The element to display the name in
**/
function getNCBIContentByID (ID, target)
{
// URL to retrieve informations from
var url = "php/get_ncbi_info.php";
// Parameters for NCBI form
var par = "id=" + ID;
var ajax = getAjaxObject ();
ajax.open ("POST", url, true);
ajax.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
ajax.setRequestHeader("Content-length", par.length);
ajax.setRequestHeader("Connection", "close");
var _taxum = ID;
var _target = target;
// Asynchronous communication with NCBI. It may be long. This callback function will be fired
// once results are available
ajax.onreadystatechange = function () { parseNCBIContent(ajax, _target, _taxum); };
// Ask NCBI for informations
ajax.send (par);
}
/**
* Communicates with NCBI to retrieve content about taxonomy
* @param name Taxon name
* @param target The element to display the name in
**/
function getNCBIContentByName (name, target)
{
// URL to retrieve informations from
var url = "php/get_ncbi_info.php";
// Parameters for NCBI form
var par = "name=" + encodeURIComponent(name) + "&srchmode=1&keep=1&a=Go";
var ajax = getAjaxObject ();
ajax.open ("POST", url, true);
ajax.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
ajax.setRequestHeader("Content-length", par.length);
ajax.setRequestHeader("Connection", "close");
var _taxum = name;
var _target = target;
// Asynchronous communication with NCBI. It may be long. This callback function will be fired
// once results are available
ajax.onreadystatechange = function () { parseNCBIContent(ajax, _target, _taxum); };
// Ask NCBI for informations
ajax.send (par);
}
function parseNCBIContent (xhr, target, taxum)
{
// Communication is done if readyState = 4, otherwise communication is in progress
if (xhr.readyState == 4)
{
// Valid HTML Code : 200 = OK, 0 = Unknown or hidden
if (xhr.status != 200 && xhr.status != 0)
return;
var lines = xhr.responseText.split ("\n");
var ret = new Array();
// Regexp to find data
var inre = /<a.*?>(.*?)<\/a>/gi;
var isID = (parseInt(taxum) == taxum);
var taxumName = "Unknown";
// For each lines
for (var li = 0, le = lines.length; li < le; li++)
{
var matched = null;
if (taxumName == "Unknown" && isID && (matched = /<title>Taxonomy browser \((.*?)\)<\/title>/i.exec (line)) != null)
{
taxumName = matched[1];
}
inre.lastIndex = 0;
var line = lines[li];
// This regexp indicates the line we are on contains useful informations about taxa
if ((/<dd>.*?<\/dd>/g).test (line))
{
// Propagation from LUCA to the end of the tree (i.e. to /taxum/)
while ((matched = inre.exec (line)) != null)
{
ret.push (matched[1]);
}
if (ret.length > 0)
break;
}
// Sometimes data may be displayed with warning, regexp is different then
else if ((/.*<strong>lineage<\/strong>.*?<\/small>.*<hr size="?1"?>/gi).test (line))
{
var matched = null;
// Same inline regexp though
while ((matched = inre.exec (line)) != null)
{
// But we need to exclude useless informations
if ((/<strong>lineage<\/strong>/i).test (matched[1]) || (/root/i).exec (matched[1]))
continue;
ret.push (matched[1]);
}
if (ret.length > 0)
break;
}
// It may be difficult to find out what the user really wants. If NCBI proposes
// multiple solution, it displays an error message and invites the user to be more specific.
else if ((/.*<li><a .*?><strong>(.*?)<\/strong><\/a><\/li>/gi).test (line))
{
// Removes the graphical rendering of the taxum
target.parentNode.removeChild (target);
// Displays an error message
if (isID)
alert ("ID #" + taxum + " corresponds to multiple entry. Please, be more specific");
else
alert (taxum + " corresponds to multiple entry. Please, be more specific");
return;
}
}
// If no informations have been found, it displays an error message.
if (ret.length == 0)
{
// Removes the graphical rendering of the taxum
target.parentNode.removeChild (target);
// Displays an error message
if (isID)
alert ("ID #" + taxum + " were not found on NCBI database");
else
alert (taxum + " could not be found on NCBI database");
}
else
{
// We add the last one not displayed in "lineage" line on NCBI website
if (isID)
ret.push (taxumName);
else
ret.push (taxum);
// Workaround for a bug to avoid uncertainties when a taxum name is used multiple time in the tree
for (var r = 0, rl = ret.length; r<rl; r++)
{
while (arrayCount (ret, ret[r]) > 1)
ret[r] += "_";
}
if (ret[0] == "cellular organisms")
ret[0] = "LUCA";
else
ret.unshift ("LUCA");
var cret = ret.join ("|");
// The line above means: if the current organism has not been added yet
if ($("#list").value.split("#").indexOf (cret) == -1)
{
target.className = "";
target.addEventListener ("click", removeSpecies, true);
if (isID)
target.innerHTML = taxumName;
if ($("#list").value == "")
$("#list").value += cret;
else
$("#list").value += "#" + cret;
updateFromCombobox();
}
else
{
target.parentNode.removeChild (target);
}
}
}
}
/**
* Displays a Taxum with a graphical rendering
* @param name Taxum name
* @param isLoading If true, display a 'loading' GIF
* @return The HTML DOM Element
**/
function addGraphicalTaxum (name, isLoading)
{
var t = document.createElement ("li");
t.innerHTML = name;
if (isLoading == true)
t.className = "loading";
$("#graphical-list").insertBefore (t, $("#graphical-list-clearer"));
return t;
}
/**
* Updates the combobox "Display from" in Advanced Settings
* according to the most recent common ancestor of the displayed taxum
**/
function updateFromCombobox ()
{
var sp = $("#list").value.split("#");
var c = new Array();
for (var s = 0, ls = (sp[0] == "" ? 0 : sp.length); s<ls; s++)
{
if (s == 0)
{
c = sp[s].split("|");
}
else
{
var ci = 0;
var t = sp[s].split("|");
for (ci = 0; c[ci] == t[ci] && ci < c.length; ci++) {}
c = c.slice (0, ci);
}
if (c.length == 0)
break;
}
var from = $("#from");
from.innerHTML = "";
if (c.length == 0)
{
from.innerHTML = "<option value='LUCA'>— Add species first —</option>";
from.disabled = true;
}
else
{
from.disabled = false;
for (var i = 0; i<c.length; i++)
{
from.innerHTML += "<option value='" + c[i] + "'>" + c[i] + "</option>";
}
}
}
/**
* Removes a taxum from the list
**/
function removeSpecies ()
{
var sp = $("#list").value.split("#");
var ta = this.innerHTML;
var nsp = "";
for (var s = 0, ls = sp.length; s<ls; s++)
{
var lineage = sp[s].split("|");
var fr = true;
if (lineage.pop() != ta)
{
if (!fr)
nsp += "|";
else
{
if (nsp != "")
nsp += "#";
fr = !fr;
}
nsp += sp[s];
}
else
{
this.parentNode.removeChild (this);
}
}
$("#list").value = nsp;
updateFromCombobox();
}
/**
* Adds a taxum in the list
**/
function addSpecies ()
{
var t = $("#species");
var l = t.value.split("\n");
for (var s = 0, le = l.length; s < le; s++)
{
var species = l[s];
if (species != "")
{
if (parseInt(species) != species)
species = species.charAt(0).toUpperCase() + species.slice (1).toLowerCase();
getNCBIContent (species, addGraphicalTaxum(species, true));
}
}
t.value = "";
}
/**
* Creates PNG or ASCII Tree
**/
function run ()
{
var sp = $("#list").value.split("#");
if (sp == "")
{
alert ("No species to display");
return;
}
// Getting settings
var extended = $("#extended").checked;
var png = $("#render_png").checked;
var pngw = parseInt($("#png_width").value);
var pngh = parseInt($("#png_height").value);
var pnglw = parseInt($("#png_linewidth").value);
var pngfs = parseInt($("#png_fontsize").value);
var ascii = $("#render_ascii").checked;
var from = $("#from").value;
if (png == false && ascii == false)
{
alert ("Please, select a render layout");
return;
}
if (png == true && (isNaN(pngw) || isNaN(pngh) || pngw == 0 || pngh == 0))
{
alert ("Please, enter a valid width and height for PNG rendering");
return;
}
if (png == true && (isNaN(pnglw) || pnglw == 0))
{
alert ("Please, enter a valid linewidth for PNG rendering");
return;
}
// Removes previous informations in the "tree"
tree.clear ();
// Sets the Root Element (and discards older ancestors)
tree.setRoot (from);
// For each taxum
for (var s = 0, ls = sp.length; s < ls; s++)
{
var lsp = sp[s].split("|");
// Discards ancestors older than the root element
while (lsp[0] != from)
{
lsp.shift();
}
// Remove the root element too
lsp.shift();
var previous = from;
for (var i = 0, li = lsp.length; i<li; i++)
{
var ct = lsp.shift();
// Last element in the lineage tree are always displayed (i.e. setStar)
if (lsp.length == 0)
tree.setStar (ct);
var children = null;
if (!tree.containsKey (previous))
{
children = new Array();
children.push (ct);
tree.put (previous, children);
}
else
{
children = tree.get (previous);
if (children.indexOf (ct) == -1)
{
children.push (ct);
}
}
previous = ct;
}
}
if (!extended)
tree.simplify();
if (ascii)
{
$("#ascii").innerHTML = "<pre>" + tree.printTree () + "</pre>";
$("#ascii").style.display = "block";
}
else
{
$("#ascii").style.display = "none";
}
if (extended)
tree.simplify();
if (png)
{
$("#png").innerHTML = "<img src='" + tree.getPNGURI (pngw, pngh, {lineWidth:pnglw, fontSize:pngfs}) + "' />";
$("#png").style.display = "block";
}
else
{
$("#png").style.display = "none";
}
}
/**
* Displays more settings for user
**/
function moreSettings ()
{
var tr = $("#png_settings").$("tr");
for (var i = 1; i<tr.length; i++)
{
if (tr[i].className != "hidden")
continue;
if (tr[i].style.display != "table-row")
tr[i].style.display = "table-row";
else
tr[i].style.display = "none";
}
}
/**
* Initialization
**/
function init ()
{
// Adds Eventlisteners
var adder = $("#adder");
adder.addEventListener ("click", addSpecies, true);
var runner = $("#run");
runner.addEventListener ("click", run, true);
var settings = $("#more_png_settings");
settings.addEventListener ("click", moreSettings, true);
// Creates tree object to manipulate lineages
tree = new Tree();
// When refreshing the page, hidden input are not cleared.
// Following piece of code read the #list input and creates GraphicalTaxum
if ($("#list").value != "")
{
var sp = $("#list").value.split("#");
for (var s = 0, ls = sp.length; s<ls; s++)
{
var lineage = sp[s].split("|");
var species = lineage.pop();
species = species.charAt(0).toUpperCase() + species.slice (1).toLowerCase();
addGraphicalTaxum (species).addEventListener ("click", removeSpecies, true);
}
}
updateFromCombobox();
}
/**
* Runs init() when webpage is ready
**/
function preinit ()
{
// If browser is too old, an error message is shown
if (!getAjaxObject ())
{
alert ("Your browser may not be able to work properly with this tool. Please, update your browser to use this tool.");
}
// While "body" is not fully loaded, it waits. Then it runs "init()"
var b = $("body")[0];
if (b != undefined)
init();
else
setTimeout (preinit, 10);
}
// Ok, go :)
var tree;
preinit();