/* 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 script updates the screen_width cookie , for PHP to reference during page generation this allows me to generate screen-width-specific HTML WITHOUT USING INLINE SCRIPTS! however it requires cookie and because it requires cookies in this manner, it also requires javascript and because JS cant be run until the first page is downloaded there is a tiny tiny drawback: the screen width that the server sees will not be refreshed until the next page that the user sees. To work around this, the screen-width-dependant PHP code in menu.php has been mirrored in menu.js.php to respace the page whenever the user resizes the browser. I go so far out of my way to avoid inline scripts because www.quirksmode.org explains that they tend to be hazardous to older netscape browsers */ /* This method returns the value which will be used for setting the "screen_width" cookie */ function get_screen_width() { /* this method uses the window resolution width to align our pictures to *\ return self.screen.width; /**/ /* this method uses the window's internal width for sizing our pictures */ return QM.getWindowInnerDim().x; /**/ } /* This method plants the cookie It will be run upon loading the page, and upon resizing the page */ function set_screenres_cookie() { var screen_width = "" + get_screen_width(); var screen_width_cookie = QM.readCookie("screen_width"); if (screen_width_cookie != screen_width) { QM.createCookie("screen_width", screen_width, 7); //seven days alive } } //always use event listeners rather than directly setting "window.onXXX" so that old onload methods dont get overwritten addListener(window, "resize", set_screenres_cookie); addListener(window, "load", set_screenres_cookie);