/* 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 Javascript, upon loading, sweeps through the DOM tree and finds any tags with classname set to "newwindowlink" For these (which the script assumes are anchor tags), a tag is created with identical innerHTML content. The span's CSS is tweaked so that it appears as an anchor tag would, and finally the span is given an onClick that opens up a new window (via Javascript, rather than which opens new windows via HTML). The newly opened window, which in our case always happens to point to "sites.php", is passed GET parameters specifying the window's dimensions, and whether the to allow javascript features. "sites.php" is specially designed to retrieve these GET parameters and adjust its content (which is a single image for the time being) to best fit the desired size. The Javascript controls which "sites.php" exhibit is a sole "close window" button. Nothing fancy. The reason this script exchanges anchors with clickable spans is that anchors MUST point to some content. The only way an anchor can be specified (in a cross-browser manner) to point to nothing is by changing its href to "#". What this truly does is redirect the link to the top of the page ,such that clicking it repositions the page scrollbar to the top. I would rather the scrollbar not change position. I believe setting the href to "" in firefox accomplsihes this desire of mine, but executing the same code in Internet Explorer causes the link to target to the root page of the current directory (index.php or whatever it is for the webserver) For non-Javascript browsers, the link also comes with parameter target="_blank", so that if this script does not initialize and create a clickable span that opens a new window, then at least the default HTML will know to open up "sites.php" in a new window - one that is opened without the GET parameters and therefore without any additional HTML control. With the non-javascript page, "sites.php" will display its containing image at 640px wide, rather than the javascript-calculated screen-dependant size. All in all, this JS file is highly specialized for the anchors that it references (those named "newwindowlink"), for the url for which they are originally pointed towards ("sites.php"), and for their pre-existing behavior (target="_blank") */ function init_newwindowlink(link) { //insert "close=true" into the GET portion of the URL //this will tell the page to generate a JS close button. //if there is no otherwise no need for it, eh? var url = urlInsertGetStmt(link.href, "close", "true"); //for now I will hard-code the popup window size to work with the size of the image contained in sites.php //so if the sites.php content changes, these numbers should change as well //the image is 1056 x 816 pixels . . . var w = self.screen.width * 0.70; //lets say we want to take up no more than 65% of the screen . . . //as it is, with a request for 890 x 720 pixels, the window takes up 898 x 769 (in firefox) //so right now we're at about 70% of the original width - for our window - and -100 for the image size - or 65% of the screen width if (w < 620) w = 620; //minimum size if (w > 1146) w = 1146; //maximum size: image size plus 90px //heck, don't bother worry about maximum size. resize.php will take care of that: it won't resize it any greater than the maximum //insert the width into the sites.php URL as well url = urlInsertGetStmt(url, "width", ""+(w-90)); //if JavaScript is allowed to execute then remove the old clickable link with target="_blank" //with a clickable span to popup a separate window //Use a clickable span in place of an anchor because anchors MUST have href's specified. //in our case the href would point to nowhere. While firefox supports href="" to denote "dont go anywhere!", //Internet Explorer will take this to mean "go to the base of this directory", which will change our page. //This means href must be specified as "#" in a browser. However if a page is directed to "#" it will //scroll to the top of the page. This is an unwanted side effect, so all in all using a span is more usable. var span = document.createElement("span"); //store these parameters for the onclick function span.windowOpenURL = url; span.windowOpenName = "annotation"; span.windowOpenFeatures = "toolbar=0,status=0,menubar=0,scrollbars=1,resizable=1,width="+w+",height="+(w*720/890); //auto-compute the height by preserving the image's aspect ratio //specify the onclick function: span.onclick = function() { //todo: for the time being all this code is written specificly for this site //one way to moreso-modularize this file is to pass all this information through either the PHP GET of /this/ page, or somehow through the id or class window.open(this.windowOpenURL, this.windowOpenName, this.windowOpenFeatures); } //copy over the innerHTML from the link to the span span.innerHTML = link.innerHTML; //copy link style info into the span. //todo: find out how to get the hover styles/pseudoclasses/etc span.style.textDecoration = "underline"; span.style.color = QM.getStyle(link, "color"); span.style.cursor = "pointer"; link.parentNode.replaceChild(span, link); } //search through the document.body DOM tree, find any tags with class="newwindowlink", //and swap them out with clickable spans that pop up our new window . . . function init_newwindowlink_system() { var links = searchDomTree(document.body, "className", "newwindowlink"); for (l in links) init_newwindowlink(links[l]); } //always use event listeners rather than directly setting "window.onload" so that old onload methods dont get overwritten addListener(window, "load", init_newwindowlink_system);