/* Olympic National Park Cryptogamic Database Copyright © 2005 Northwest Alliance for Computational Science and Engineering (NACSE, www.nacse.org), based at Oregon State University. The design and implementation of Olympic National Park Cryptogamic Database are available for royalty-free adoption and use for non-commercial purposes, by any public or private organization. Copyright is retained by NACSE and Oregon State University. Redistribution of any portion or of any derivative works must include this notice. Please address comments or questions about commercial use to nacse-questions@nacse.org. */ /* Olympic National Park Cryptogamic Database Copyright © 2005 Northwest Alliance for Computational Science and Engineering (NACSE, www.nacse.org), based at Oregon State University. The design and implementation of Olympic National Park Cryptogamic Database are available for royalty-free adoption and use for non-commercial purposes, by any public or private organization. Copyright is retained by NACSE and Oregon State University. Redistribution of any portion or of any derivative works must include this notice. Please address comments or questions about commercial use to nacse-questions@nacse.org. */ /* This is a useful library of JavaScript functions I have either developed or found elsewhere on the internet. */ // ******************************** quirksmode scripts: ******************************** // //This content was derived from http://www.quirksmode.org var QM; if (!QM) QM = new function() { //Browser detect - http://www.quirksmode.org/js/detect.html this.checkIt = function(string) { this.place = this.detect.indexOf(string) + 1; this.thestring = string; return this.place; } this.detect = navigator.userAgent.toLowerCase(); this.OS = 0; this.browser = 0; this.version = 0; this.total = 0; this.place = 0; this.thestring = 0; if (this.checkIt('konqueror')) { this.browser = "Konqueror"; this.OS = "Linux"; } else if (this.checkIt('safari')) this.browser = "Safari" else if (this.checkIt('omniweb')) this.browser = "OmniWeb" else if (this.checkIt('opera')) this.browser = "Opera" else if (this.checkIt('webtv')) this.browser = "WebTV"; else if (this.checkIt('icab')) this.browser = "iCab" else if (this.checkIt('msie')) this.browser = "Internet Explorer" else if (!this.checkIt('compatible')) { this.browser = "Netscape Navigator" this.version = this.detect.charAt(8); } else this.browser = "An unknown browser"; if (!this.version) this.version = this.detect.charAt(this.place + this.thestring.length); if (!this.OS) { if (this.checkIt('linux')) this.OS = "Linux"; else if (this.checkIt('x11')) this.OS = "Unix"; else if (this.checkIt('mac')) this.OS = "Mac" else if (this.checkIt('win')) this.OS = "Windows" else this.OS = "an unknown operating system"; } //get style code - I changed it from being an 'id' reference to an object reference (for ease of use) this.getStyle = function(x/*el*/,styleProp) { //var x = document.getElementById(el); if (x.currentStyle) var y = x.currentStyle[styleProp]; else if (window.getComputedStyle) var y = document.defaultView.getComputedStyle(x,null).getPropertyValue(styleProp); return y; }; // this works even without a following semicolon . . . //get window inner dimensions - I changed it to return an object containing 'x' and 'y' variables (which dont exist if any detects failed) this.getWindowInnerDim = function() { var dim = new Object(); if (self.innerHeight) // all except Explorer { dim.x = self.innerWidth; dim.y = self.innerHeight; } else if (document.documentElement && document.documentElement.clientHeight) // Explorer 6 Strict Mode { dim.x = document.documentElement.clientWidth; dim.y = document.documentElement.clientHeight; } else if (document.body) // other Explorers { dim.x = document.body.clientWidth; dim.y = document.body.clientHeight; } return dim; }; this.createCookie = function(name,value,days) { if (days) { var date = new Date(); date.setTime(date.getTime()+(days*24*60*60*1000)); var expires = "; expires="+date.toGMTString(); } else var expires = ""; document.cookie = name+"="+value+expires+"; path=/"; } this.readCookie = function(name) { var nameEQ = name + "="; var ca = document.cookie.split(';'); for(var i=0;i < ca.length;i++) { var c = ca[i]; while (c.charAt(0)==' ') c = c.substring(1,c.length); if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); } return null; } this.eraseCookie = function(name) { createCookie(name,"",-1); } //Despite all recommendations to eliminate browser-detects, this is a //recommended addition by Mission Impossible: http://evolt.org/article/Mission_Impossible_mouse_position/17/23335/index.html var isOpera = (navigator.userAgent.indexOf('Opera') != -1); var isIE = (!isOpera && navigator.userAgent.indexOf('MSIE') != -1); function getMousePagePos(e) { var pos = {x:0, y:0}; if (!e) var e = window.event; if (e.pageX || e.pageY) { pos.x = e.pageX; pos.y = e.pageY; } else if (e.clientX || e.clientY) { pos.x = e.clientX; pos.y = e.clientY; if (isIE) { pos.x += document.body.scrollLeft; pos.y += document.body.scrollTop; } } return pos; } }; // ******************************** listener library: ******************************** // //Add a listener to the specified object. //Todo - if neither is supported . . . revert to oldschool src["on"+ev] = function() { cycle through all func's in some array and execute them } //but to do that - for the oldschool support to work - we would need an event listener init . . . and perhaps an event listener object? (much as mouse is handled below) function addListener(src, ev, func) { //W3C DOM Event Handling if (src.addEventListener) { src.addEventListener(ev, func, false); } //Microsoft . . . else if (src.attachEvent) { src.attachEvent("on" + ev, func); } } // ******************************** mouse library: ******************************** // //mouse handling object //what happens if multiple instances of this are php-included? //how about this? "var mouseh;" "if (!mouseh) mouseh = . . . " ? this depends upon the assumption that mouseh == null by default //this way even if the code is php-included twice, it will still only instanciate a single object //Notice how I wrote my own multiple-event-handler code in there? thats not necessarily the best. //pro: can be easily made backwards compatible. con: a lot of extra (possibly wasted) typing var mouseh; if (!mouseh) mouseh = { //last stored mouse position lastpos:{x:0, y:0}, //array of functions accepting all cross-browser mouse parameters onMouseUpFuncs: [], onMouseDownFuncs: [], onMouseMoveFuncs: [], addUpFunc: function (func) { this.onMouseUpFuncs.push(func); }, addDownFunc: function (func) { this.onMouseDownFuncs.push(func); }, addMoveFunc: function (func) { this.onMouseMoveFuncs.push(func); }, // ******** begin callback code ******** //notice - within callback code 'this' refers to the callee, not mouseh, so you gotta reference mouseh specifically onMouseUp: function (e) { var pos = QM.getMousePagePos(e); for (i in mouseh.onMouseUpFuncs) { mouseh.onMouseUpFuncs[i](pos.x,pos.y); } }, onMouseDown: function (e) { var pos = QM.getMousePagePos(e); //cycle through all preregistered functions and execute them . . . for (i in mouseh.onMouseDownFuncs) { mouseh.onMouseDownFuncs[i](pos.x, pos.y); } }, onMouseMove: function (e) { var pos = QM.getMousePagePos(e); //cycle through all preregistered functions for (i in mouseh.onMouseMoveFuncs) { mouseh.onMouseMoveFuncs[i](pos.x, pos.y); } //store the last mouse coords mouseh.lastpos.x = x; mouseh.lastpos.y = y; }, // ******** end callback code ******** hasMouseBeenInitialized: false, init: function () { if (this.hasMouseBeenInitialized) return; if (ie4) { document.captureEvents(Event.MOUSEMOVE | Event.MOUSEDOWN | Event.MOUSEUP | Event.MOUSEDBLCLICK); } addListener(document, "mousedown", this.onMouseDown); addListener(document, "mousemove", this.onMouseMove); addListener(document, "mouseup", this.onMouseUp); this.hasMouseBeenInitialized = true; } }; // ******************************** browser-specific ******************************** // //http://clagnut.com/sandbox/imagefades/ //todo - only set the single required property from there rather than the excessive property setting as we have it function setOpacity(obj, opacity) { //some Firefaux versions cause a flicker when the alpha is set to 100% if (opactiy > 99.999) opacity = 99.999; //IE/Win if (obj.style.filters) { // IE/Win obj.style.filter = "alpha(opacity:"+opacity+")"; //oldschool IE/Win? if (obj.style.filters.item) { obj.style.filters.item("DXImageTransform.Microsoft.Alpha").opacity = opacity; } //Here's another IE/Win method: if (obj.style.filters.alpha && obj.style.filters.alpha.opacity) { obj.style.filters.alpha.opacity = opacity; } } // Safari<1.2, Konqueror obj.style.KHTMLOpacity = opacity/100; // Older Mozilla and Firefox obj.style.MozOpacity = opacity/100; // Safari 1.2, newer Firefox and Mozilla, CSS3 obj.style.opacity = opacity/100; } function getPageScrollOffsetX() { if (window.innerHeight) { return window.pageXOffset; } else if (document.documentElement && document.documentElement.scrollTop) { return document.documentElement.scrollLeft; } else if (document.body) { return document.body.scrollLeft; } return 0; } function getPageScrollOffsetY() { if (window.innerHeight) { return window.pageYOffset; } else if (document.documentElement && document.documentElement.scrollTop) { return document.documentElement.scrollTop; } else if (document.body) { return document.body.scrollTop; } return 0; } // ******************************** generic functions ******************************** // //how to grab the absolute position of some HTML object function getAbsolutePosition(n) { var pos = {x:0, y:0}; do { pos.x += n.offsetLeft; pos.y += n.offsetTop; } while (n = n.offsetParent); return pos; } //Recursive call for "searchDOMTree". can we make this method private to this file only? function searchDomTreeR(node, key, val, child, dest) { //see if this one works if (val == node[key]) { dest.push(node); if (!child) return; //if we aren't supposted to search children then stop } //test all its children for (var ch = node.firstChild; ch; ch = ch.nextSibling) { searchDomTreeR(ch, key, val, child, dest); } } //function for searching through the DOM tree and returning all of objects for which one property equals one value //search for all UL nodes with matching className. do not search the children of parents we have found //node: which node to start at //key: what key to pick out //val: the sought after value of the specified key //child: boolean flag whether to search children of found objects function searchDomTree(node, key, val, child) { var dest = []; searchDomTreeR(node, key, val, child, dest); return dest; } //same as above but with an array rather than a DOM tree function searchArray(ar, key, val) { var dest = []; for (var i = 0; i < ar.length; i++) { var node = ar[i]; if (node[key] == val) dest.push(node); } return dest; } function searchDomTreeRegExpR(node, key, val, child, dest) { var test = val.exec(""+node[key]); if (test) { //force-string-conversion dest.push( //an object, with 'match' the matching node and 'exec' the regexp exec { match:node, exec:test } ); if (!child) return; } //test all its children for (var ch = node.firstChild; ch; ch = ch.nextSibling) { searchDomTreeRegExpR(ch, key, val, child, dest); } } //works the same as searchDomTree, but accepts a regular expression for "val" rather than a string function searchDomTreeRegExp(node, key, val, child) { var dest = []; searchDomTreeRegExpR(node, key, val, child, dest); return dest; } //same as above but with an array rather than a DOM tree function searchArrayRegExp(ar, key, val) { var dest = []; for (var i = 0; i < ar.length; i++) { var node = ar[i]; var test = val.exec(""+node[key]); if (test) { dest.push( { match:node, exec:test } ); } } return dest; } /* DOM tree searcher node is a node in the DOM tree key is a string for a key for the node object value is a value object if a regexp, converts tested objects into a string and regexp tests it if a string, converts tested objects into strings and equivalence tests it if an object, does an equivalence (ref-based) test child means whether to search children of successful objects content is accumulated in this.result, and returned. */ function domTreeSearch(node, key, value, child) { //no overloading parameters -- except via default (null/false/etc) values //store ext params this.node = node; this.key = key; this.value = value; this.child = child; //init int ones (should this be private? how do private vars work? just by saying 'var result = ' ? ) this.search = function() { this.result = []; if (this.node) this.searchRecursive(this.node); return this.result; } this.searchRecursive = function(node) { var test; if (this.value.__proto__ == "/(?:)/") { //value is a regexp - so do its test test = this.value.test(""+node[this.key]); } else if (this.value.__proto__ == "") { //value is a string - so do string compare test = this.value == ""+node[this.key]; } else { //value is an object so do immediate compare test = this.value == node[this.key]; } if (test) { this.result.push(node); if (!this.child) return; } for (var ch = node.firstChild; ch; ch = ch.nextSibling) { this.searchRecursive(ch); } }; }; //remove a single object from an array function removeFromArray(ar,ob) { var i; var found = false; for (i = 0; i < ar.length; i++) { if (ar[i] == ob) { ar.splice(i,1); i--; found = true; } } return found; } //remove all children from a DOM tree object function removeAllChildren(obj) { while (obj.firstChild) { obj.removeChild(obj.firstChild); } } //the same ol' insert before, without looking up the parent node! function insertBefore(before, insert) { var parent = before.parentNode; if (!parent) return;//alert("insertBefore found no parent!"); parent.insertBefore(insert, before); } // a new approach: inserting after an element function insertAfter(after, insert) { var parent = after.parentNode; if (!parent) return;//alert("insertAfter found no parent!"); var ns = after.nextSibling; if (ns) { parent.insertBefore(insert, ns); } else { parent.appendChild(insert); } } //appending a child to the front of a list function appendChildToFront(parent, ch) { var fc = parent.firstChild; if (!fc) { parent.appendChild(ch); } else { parent.insertBefore(ch, fc); } } //debug alert prints out all properties of a variable //o - the object for which to print info about //n - a name string to append to the beginning of the output //returnStr - a boolean value whether to return the debug content as a string or (by default) to output it as an alert box function debugAlert(o,n, returnStr) { var str = ""; if (n) str = n + ":\n"; for (i in o) { str += i + " = " + o[i] + "\n"; } if (returnStr) { return str; } else { alert(str); } } //returns a 2-piece array of str. splits it by the first occurance of "splitter" function splitOnce(str, splitter) { var index = str.indexOf(splitter); if (index == -1 || index == str.length-1) return [str, ""]; return [str.substring(0, index), str.substring(index+1)]; } //returns a combination of the two strings with 'divide' placed between them only if the suffix exists function combineSplitOption(prefix, splitter, suffix) { if (!suffix || !suffix.length) return prefix; return prefix + splitter + suffix; } //takes in a url string and returns an object cnotaining the components: // base - string of whatever precedes the '?' // get[] - points to an array of the '&'/'=' -separated GET key/values // key - string of the GET key // value - string of the GET value // anchor - whatever proceeds the '#' function urlSplit(url) { var urlObj = {}; //separate 'url' into 'baseurl' and 'anchor' var a = splitOnce(url, "#"); var baseurl = a[0]; urlObj.anchor = a[1]; //separate 'url' into 'base' and 'get' a = splitOnce(baseurl, "?"); urlObj.base = a[0]; var get = a[1]; urlObj.get = []; var index; while ((index = get.indexOf("=")) != -1) { //while we find an "=" in the string . . . //search for an "&" to denote the end of the value var end = get.indexOf("&", index); if (end == -1) end = get.length+1; var kv = { key: get.substring(0, index), value: get.substring(index+1,end-1) }; urlObj.get.push(kv); //now process the rest get = get.substring(end); } return urlObj; } function urlGetValue(url, key) { var uo = urlSplit(url); for (var i = 0; i < uo.get.length; i++) { var kv = uo.get[i]; if (kv.key == key) return kv.value; } } function urlMerge(urlObj) { var get = "" var div = ""; for (var i = 0; i < urlObj.get.length; i++) { var kv = urlObj.get[i]; get = get + div + kv.key + "=" + kv.value; //append our key/value to the string . . . div = "&"; //set our divider for all but the first pass } //re-collect baseurl from base and get var baseurl = combineSplitOption(urlObj.base, "?", get); //re-collect url from baseurl and anchor return combineSplitOption(baseurl, "#", urlObj.anchor); } //seems like a useful function to keep around //inserts &key=value into the get statement of a url //should this be responsible for url-encoding keys and values? or should the calling function be? function urlInsertGetStmt(url, key, value) { //separate 'url' into 'baseurl' and 'anchor' var a = splitOnce(url, "#"); var baseurl = a[0]; var anchor = a[1]; //separate 'url' into 'base' and 'get' a = splitOnce(baseurl, "?"); var base = a[0]; var get = a[1]; //now search out any existance of our key= in the get portion of the url //this should be do-able via splitOnce, right? sorta . . .i'd like to know if the splitOnce finds the splitter at the beginning, middle, or end var index = get.indexOf(key+"="); if (index == -1) { //no key exists . . . direct-insert into the beginning get = key + "=" + value + (get.length ? "&" + get : ""); //only add the & if its needed } else { //key does exist! extract and replace the value var getPrefix = get.substring(0, index); var getSuffix = null; var endIndex = get.indexOf("&", index); if (endIndex == -1 || endIndex == get.length-1) { //if the & doesnt exist, or is at the end, then suffixsuffix be empty getSuffix = ""; } else { //push one past the & sign getSuffix = get.substring(endIndex+1); } get = combineSplitOption(getPrefix + key + "=" + value, "&", getSuffix); } //re-collect baseurl from base and get baseurl = combineSplitOption(base, "?", get); //re-collect url from baseurl and anchor url = combineSplitOption(baseurl, "#", anchor); return url; } /* #This page handles all my custom JavaScript for the "menu.php" page The specific tasks it does are: 1) add in [clear] buttons to each of the four main query tables 2) add sortable columns to the site result table 3) add in the "go back" button into the main table button interface 4) add in any content subset controls (i.e. the "prev / next" buttons) 1) The purpose of the [clear] buttons for each of the main tables is to allow users an option to deselect any content they have clicked on. Currently the only means of deselecting all entries in a select box is by (1) clicking a single entry (thus deselecting all else) and (2) ctrl+clicking that same entry (to deselect it, finally). This seems a bit ambiguous, so I thought I'd add in helper controls for quickly clearing all. 2) Sort tables simply seemed like a cool feature to add 3) The 'Go Back' button is added through JavaScript rather than being embedded within the HTML because if the browser didn't support HTML then there would be no point for it to have a "Go Back" button sitting there doing nothing. 4) The prev/next subset control features require JavaScript so that, when the user changes the number of entries to display per page, the document will automatically update to reflect the changes. */ /* This method insert [clear] buttons beside the titles of each of the select boxes. Sort through all the select's. If it finds any, add to that to a
then two links: one to select all, the other to select none. */ function init_crypto_maintable(table) { //grab all select tags in the specified table var ar = searchDomTree(table, "nodeName", "SELECT"); for (var i=0; i < ar.length; i++) { var s = ar[i]; //create our clickable span var clear = document.createElement("span"); //set the span's imitation-link styles: clear.style.color = QM.getStyle(document.links[0], "color"); clear.style.cursor = "pointer"; clear.style.textDecoration = "underline"; //and the rest: clear.style.fontSize = "10pt"; //clear.style.fontWeight = "bold"; clear.style.cssFloat = "right"; clear.style.textAlign = "right"; clear.style.display = "inline"; //maybe this is why it isnt working in IE... clear.style.paddingRight = "15px"; clear.innerHTML = "[Clear]"; clear.targetSel = s; //keep track of what select box we're pointing towards clear.onclick = function() { this.targetSel.selectedIndex = -1; } //exactly where the node is inserted makes quite a difference between browsers . . . var p = s.parentNode; p.insertBefore(clear, p.childNodes[1]); } } /* These variables are used by the "sortingTableCompare" method for keeping track of what table we are to be sorting, and which row we are sorting it by */ var sortingTable = null; //the currently-sorted table var sortingTableIndex = -1; //its currently-sorting column var sortingUseNumbers = false; //the compare function for our sorting table's sort operation function sortingTableCompare(a, b) { //always let that the row of headers remains on the top. //" I suggest a new strategy. Let the trs[0] win" if (a == sortingTable.trs[0] && b == sortingTable.trs[0]) return 0; if (a == sortingTable.trs[0]) return -1; if (b == sortingTable.trs[0]) return 1; //otherwise extract the value and compare //get the value of the sortingTableIndex for column a var va = ""; if (a.chs[sortingTableIndex]) { //if the sortingTableIndex column exists var c = a.chs[sortingTableIndex]; //grab it if (c && c.firstChild) { //if it possess a first child va = c.innerHTML; //use its first child's inner html for our compare data } } //get the value of the sortingTableIndex for column b var vb = ""; if (b.chs[sortingTableIndex]) { var c = b.chs[sortingTableIndex]; if (c && c.firstChild) { vb = c.innerHTML; } } //compare them - via number or string means if (sortingUseNumbers) { var fa = parseFloat(va); var fb = parseFloat(vb); //strings containing only spaces will be treated the same as negative infinity zillion if (!(/[^ ]/.test(va))) fa = -1;//.5E+339 * 2.4E+317; if (!(/[^ ]/.test(vb))) fb = -1;//.5E+339 * 2.4E+317; if (fa == fb) return 0; if (fa < fb) return -sortingTable.sortScalar; return sortingTable.sortScalar; } if (va == vb) return 0; if (va < vb) return -sortingTable.sortScalar; return sortingTable.sortScalar; } //upon clicking a column header, this method is called to sort all the rows by this column function sort_siteTable_by_column(siteTable, index, useNumberSorting) { //if we click on a different column, always forward-sort it if (siteTable.sortLastIndex != index) { siteTable.sortScalar = 1; } //if we click twice on the same column, flip its sorting direction else { siteTable.sortScalar *= -1; } //keep track of these, for the sorting function callback sortingTable = siteTable; sortingTableIndex = index; sortingUseNumbers = useNumberSorting; //call the array sorting algorithm siteTable.trs.sort(sortingTableCompare); //remember what index we sorted last, for clicking-to-reverse the sorting direction siteTable.sortLastIndex = index; //re-add the rows to the parent in their sorted order var parent = siteTable.trs[0].parentNode; removeAllChildren(parent); parent.appendChild(siteTable.trs[0]); for (var i = 0; i < siteTable.trs.length; i++) { parent.appendChild(siteTable.trs[i]); } } //prepare our table for sorting by the user function init_crypto_sitetable(table) { //if we find the sitetable's parent div then give it a fixed width equal to the sitetable width: var div = table.parentNode; if (div.nodeName == "DIV") { div.style.width = table.offsetWidth + "px"; } //now margin:auto will center the table's div for us. //however, without javascript, the table won't center. //margin:auto automatically centers content only if it has fixed widths //however, how do you center content that has a variable width? //what do we do with this table? //1) build an array from its rows //2) change its first row content onclick property to sort by the appropriate column //store for callback's sake table.sortLastIndex = -1; table.sortScalar = 1; //insert some helper text into the page right above the table //only insert this into the page if its message is applicable //Click column titles to sort columns
var guide = document.createElement("span"); guide.style.fontSize = "10pt"; guide.innerHTML = "Click column titles to sort rows"; table.parentNode.insertBefore(guide, table); //extract all tr elements . . . var trs = searchDomTree(table, "nodeName", "TR"); //store for callback's sake table.trs = trs; //the first element will be a tr with all the headers as children. //make it so clicking these sorts the columns //create an array of all the first-level children of each immediately under the table for (var i = 0; i < trs.length; i++) { var chs = []; for (var ch = trs[i].firstChild; ch; ch = ch.nextSibling) { //only put TH and TD elements onto the list //otherwise, if we added all elements, we'd be inserting empty-space texts and comments and what not . . . if (ch.nodeName == "TH" || ch.nodeName == "TD") { chs.push(ch); } } //hook it to the first tr trs[i].chs = chs; } //cycle through it and make all the header table entries clickable for sorting for (var i = 0; i < trs[0].chs.length; i++) { var th = trs[0].chs[i]; th.columnIndex = i; //keep track of which span this is - for sorting's sake th.sourceTable = table; //also for sorting's sake, keep track of which table owns this span //find out if we are going to sort numbers or strings var isString = true; var allEmpty = true; for (var j = 1; j < trs.length; j++) { //assert(trs[j].chs.length == trs[0].chs.length); //division only works with numbers (even strings representing numbers) //so if we divide and get NaN then we know it is a string var contents = "" + trs[j].chs[i].innerHTML; allEmpty &= !(/[^ ]/.test(contents)); //can't use ! before a // regexp isString &= isNaN(contents / 1); } //if none of them are strings, and at least one of them isnt empty, then store this as 'we are going to sort using numbers' th.isNumber = !isString && !allEmpty; //debug - say if were sorting by numbers //if (th.isNumber) trs[0].chs[i].innerHTML += " isNumber"; //if (isString) trs[0].chs[i].innerHTML += " isString"; //if (allEmpty) trs[0].chs[i].innerHTML += " allEmpty"; //make it so, upon the span's click, sort the columns th.onclick = function() { //pass the sorting function 1) the table to sort 2) which column to sort it by, and 3) whether to sort via numbers (true) or via strings (false) sort_siteTable_by_column(this.sourceTable, this.columnIndex, this.isNumber); }; //remove the link from the HTML th.style.cursor = "pointer"; th.title = "Click to Sort"; } } /* This inserts a 'go back' button right before the 'new query ' button This is done only through JS because if javascript isnt working then there will be no point in having the button in the page =) */ function init_crypto_goback() { //first, find all inputs var inputs = searchDomTree(document.body, "nodeName", "INPUT"); if (!inputs || !inputs.length) return; //of them all, find the one with the name, "reset" inputs = searchArray(inputs, "name", "reset"); //insert our new "go back" button right before the "reset' button if (!inputs || !inputs.length) return; var i = inputs[0]; if (!i) return; //construct the button var b = document.createElement("input"); b.type = "button"; b.value = "Go Back"; b.onclick = function() { history.back(); } //insert it right before the "reset" button i.parentNode.insertBefore(b, i); //keep the spacing consistent between all the buttons //Notice, we create " ", not "\n". both do the same in firefox, but in IE "\n" gives us twice the space i.parentNode.insertBefore(document.createTextNode(" "), i); } /* This initializes the subset controls within the page. Subset controls consist the "prev" and "next" buttons, the quick-jump-to various pages, and the dropdown combo box that allows the user to select how many results to display per page. The javascript code below finds all the dropdown combo boxes that control the results-per-page and modifies them to, upon user click, automatically refresh the page with the newly desired combo box. It does so by, upon user click, reading in the potential destination URL (stored in the parent form's action) and inserting into it a GET parameter to create the newly desired URL. There is a prereq coded into menu.php that the select's name should have a prefix "subset_size:", While the string in the name preceding this is to be used as the select box's identifier. */ function init_crypto_subset_ctrls() { //to accomodate for multiple subset controls per page, search for all IDs //with "subset_size:" as a prefix - rather than searching for one single string. var prefix = "subset_size:" //keep track of the prefix string here - it'll be useful later on var regexp = new RegExp(prefix); //make a regexp out of it for searching's sake var selects = searchDomTree(document.body, "nodeName", "SELECT"); //first find all selects in the page selects = searchArrayRegExp(selects, "name", regexp); //next filter out all the ones that match the regexp for (var i = 0; i < selects.length; i++) { var sel = selects[i].match; //get the object that matched the search var rr = selects[i].exec; //get the associated regexp object //find what in the ID comes after the prefix. this will be the controls' identifier. save it for URL relinking sel.ident = sel.name.substring(rr.index + prefix.length); //okay now 'sel' is our select with matching name. it's form's action is our desturl. store it in the select node sel.trueHref = sel.form.action; //upon user click relink to the source page, but insert the ?subset_size:= string into the url sel.onchange = function() { location.href = urlInsertGetStmt( this.trueHref, //take the old destination href escape("subset_size:"+this.ident), //insert our subset size parameter this.options[this.selectedIndex].value //specify its value ); } } } //init the photo table //set its size to match the page size //and add in a resize event listener //to auto resize all the thumbnails with the browser var photodiv = null; var phototable = null; var photoThumbWidth = 0; //the parameters of this code must match that found in the beginning of $screen == "photogallery" in the menu.php page //they are: // photoGalleryDivWidth = screen width - 40 // photoGalleryMarginCoeff = 0.25 // thumbsPerRow = 5 //derived values are: // photoGalleryThumbWidth = floor(photoGalleryDivWidth / (thumbsPerRow + (thumbsPerRow-1) * photoGalleryMarginCoeff)); //For development's sake, later, if you wanted, you could pass these values at GET parameters to this PHP file //and have them generated from the original PHP variables in the menu.php file function photogallery_resize() { //the get_screen_width() reference will only work if screenres.js.php has been included before this file var photoGalleryDivWidth = get_screen_width(); photoGalleryDivWidth -= 40; var photoGalleryMarginCoeff = 0.25; var thumbsPerRow = 5; var photoGalleryThumbWidth = parseInt(photoGalleryDivWidth / (thumbsPerRow + (thumbsPerRow-1) * photoGalleryMarginCoeff)); //set the div and the table photodiv.style.width = photoGalleryDivWidth + "px"; phototable.style.width = photoGalleryDivWidth + "px"; //now set the width of all the s in the table var ar = searchDomTree(phototable, "nodeName", "TD"); for (var i = 0; i < ar.length; i++) { var td = ar[i]; td.style.width = photoGalleryThumbWidth + "px"; td.style.height = photoGalleryThumbWidth + "px"; //inside each there should be an element . . . //it references resize.php. the width and height GET parametesr are to be modified to be 'photoGalleryThumbWidth' //everything else is to remain the same var ar2 = searchDomTree(td, "nodeName", "IMG"); for (var j = 0; j < ar2.length; j++) { var img = ar2[j]; //quickly rescale the image before the truly-resized image is downloaded //this code must somewhat match the size calculation code in resize.php //because resize.php is going to be generating the image to be placed in our tag var src_x = img.offsetWidth; var src_y = img.offsetHeight; //grab the newly specified width & height var width,height; //if we are requested to constrain our dimensions . . . //take the largest scale-down ratio and use that if (src_x > src_y) { width = photoGalleryThumbWidth; height = photoGalleryThumbWidth * src_y / src_x; } else { width = photoGalleryThumbWidth * src_x / src_y; height = photoGalleryThumbWidth; } img.width = ""+width; img.height = ""+height; //now download the image //only do this if we're growing in size. otherwise there's no need to grab a smaller version from the server - the client can handle that as it is. //best yet - why dont we keep the img.src pointed at the largest-yet used site (none of this size-up/size-down downloading . . .) //however this feature is not necessarily working. //if (photoGalleryThumbWidth > photoThumbWidth) { //alert("sizing up to " + photoGalleryThumbWidth); var src = img.src; src = urlInsertGetStmt(src, "width", ""+photoGalleryThumbWidth); src = urlInsertGetStmt(src, "height", ""+photoGalleryThumbWidth); //refresh the image according to its new size img.src = src; //implement a javascript callback such that, when the image loads, it resets the image width & height fields . . . img.onload = function() { //how do we get rid of these keys & values alltogether? //this.removeProperty("width"); //this.removeProperty("height"); } photoThumbWidth = photoGalleryThumbWidth; } } } } function init_crypto_photogallery() { photodiv = document.getElementById("photodiv"); if (!photodiv) return; phototable = document.getElementById("phototable"); if (!phototable) return; //exract the width= parameter here, so that we only re-download images when they are growing in size var ar = searchDomTree(phototable, "nodeName", "TD"); photoThumbWidth = parseInt(ar[0].style.width); //only add the resize listener if we found the "photodiv", which wraps the whole photo gallery addListener(window, "resize", photogallery_resize); } //init all our controls here function init_crypto() { //add in "[clear]" buttons to the maintable's selects var table = document.getElementById("maintable"); if (table) init_crypto_maintable(table); //add in the "go back" button to the main controls init_crypto_goback(); //add in the auto-refresh upon changing the subset size init_crypto_subset_ctrls(); //center the sitetable and make its columns sortable var table = document.getElementById("sitetable"); if (table) init_crypto_sitetable(table); //add in a script to auto resize the photo gallery as the browser does so as well init_crypto_photogallery(); } //attach the listener for our init method addListener(window, "load", init_crypto);