. | . | . | . | David McCracken |
Javascript Examplesupdated:2016.07.13 |
These are some of the javascript functions that I have written for use on this website. They range from quite general purpose to highly specialized. They are grouped here by the files that contain them on this website. Large comment blocks have been moved from code to text to improve readability.
ubercookie.js function getCookie( name ) { cookies = document.cookie; //alert( "cookie list: " + cookies ); var start = cookies.indexOf( name + "=" ); if (start == -1) return null; // The named cookie doesn't exist. start = cookies.indexOf( "=", start) + 1; // First char of value. var end = cookies.indexOf( ";", start); // Find the end of the value string. if (end == -1) // If the cookie doesn't have additional parameters end = cookies.length; // then the end is the end of the string. // Get the cookie value, restoring any escaped spaces to actual spaces. var value = unescape( cookies.substring( start, end )); if( value == null ) return null; else return value; } function delCookie( name ) { if( name ) setCookie( name, "", -1, 3, false ); // Empty val and yesterday. else { var cookies = document.cookie; var end = cookies.length; for( var lim = 0 ; lim < 30 ; lim++ ) // Limit iterations in case of error. { end = cookies.lastIndexOf( "=", end ); if( end == -1 ) break; start = cookies.lastIndexOf( ";", end ) + 1; // -1 -> 0, others ";" -> first char. setCookie( cookies.substring( start, end ), "", -1, 3, false ); end = start - 1; } } } function setCookie( name, val, lifetime, interval, reloadAlert, path ) { if( lifetime == 0 ) document.cookie = name + "=" + val; else { var exp = new Date(); var expiration = exp.getTime() + ( interval == 1 ? lifetime : 1000 * ( interval == 2 ? lifetime : 86400 * lifetime )); exp.setTime( expiration ); document.cookie = name + "=" + val + "; expires=" + exp.toGMTString() + ( path == null ? "" : "; path=" + path ); } if( reloadAlert != null ) { if( reloadAlert != "" ) { var ref = document.getElementById( reloadAlert ); if( ref != null ) ref.style.display = "block"; } /* Force page reload */ history.go(0); location.reload( true ); } }
ubercookie.js contains general-purpose cookie functions. Cookies have an
unusual asymmetrical assignment. Assigning a cookie name value pair to the
DOM’s document.cookie
only sets one cookie but reading back
document.cookie retrieves all of the domain’s cookies. Reading an individual
cookie entails first parsing the cookie list to find the given cookie and then
parsing the value from the remainder of the list. Even writing, which is much
simpler than reading, is not trivial. My functions setCookie, getCookie, and
delCookie are essentially access methods and destructor for a single-cookie
class. Note that various platforms limit the number of single cookies,
typically to 20 per domain.
Get a cookie’s value.
Returns: null if the cookie doesn’t exist or has an empty value.
Arguments: name (string) is the cookie name.
Retrieve the entire domain’s cookie list from document.cookie. Parse this for
the given cookie name and, finding a match, parse the value from the remainder
of the list.
Delete one or all cookies in this domain.
Returns: nothing
Arguments: name (string) is the cookie name, 0 to delete all.
Each cookie is deleted by assigning it a null value with expiration time of
yesterday. All cookies (of this domain) are deleted by parsing the cookie list
backwards, deleting each name. Backwards parsing is used so that the cookie
list only needs to be read once. Whether deleting causes the list to
immediately change or not is irrelevant since the earlier parts won’t change in
any case. This makes the process immune to platform variations.
Set the value of the given cookie
Returns: nothing
Arguments:
Every browser requires a unique means of forcing page reload and most are mutually exclusive. setCookie invokes both history.go(0) and location.reload( true ) which I have found by experiment to work for most Browsers that have any means at all and gets some, e.g. FF, to return to the current URL fragment after reload.
sizecookie.js SizeCookie = "textsize" function setSize( val, idAlert ) { setCookie( SizeCookie, val, 30, 3, idAlert == null ? "" : idAlert, "/" ); } /**** Automatic statements. These execute as the file is loaded. *******/ var sizeVal = getCookie( SizeCookie ); /* The following must be located at the end of a script block. It will screw * up any subsequent script code in the same block because it writes HTML code * immediately into the page. */ if( sizeVal ) document.write( '<link rel="stylesheet" type="text/css" href="' + ( typeof StyleRoot == "undefined" ? "" : StyleRoot ) + sizeVal + '.css" >' );
sizecookie.js supports a web page text size control means that I developed as a superior alternative to the native text size control of most browsers. Resizing all text typically produces an inferior layout even in fluid pages (as all of mine are) because it treats special purpose text, particularly in sidebar lists, and more general content in the same way. In most cases, a user only wants to change the size of the content text to either increase its readability or to see more with less scrolling. In my approach, the size of text is set by its container class, as defined in a CSS file. Any amount of specificity can be supported. In my portfolio, selecting a text size from the dropdown menu invokes the setSize function, which writes the corresponding cookie value and then forces the page to reload using the new size CSS file. Reload only happens this one time. Any pages subsequently opened use the selected CSS file as they load. This is provided by javascript code that executes immediately as the web page loads sizecookie.js.
This requires ubercookie.js for general functions. See styleCookie.js for any kind of style other than text size. Assumptions are:
Set the textsize cookie
Returns: nothing
Arguments:
menu.js function showBlock( id ) { document.getElementById( id ).style.display = "block"; } function hideById( id ) { document.getElementById( id ).style.display = "none"; } function openMenu( id, cook, pre ) { var val, lip, begin, ref; ref = document.getElementById( id ); if( ref != null ) { if( ref.style.display == "block" ) ref.style.display = "none"; // Close open menu. else // Open closed menu. { if( cook != null && ( val = getCookie( cook )) != null ) { if( pre == null ) pre = "("; // Assume e.g. href="javascript:setSize( 'tiny'..." for( var idx = 0 ; idx < ref.childNodes.length ; idx++ ) { lip = ref.childNodes[ idx ]; if( lip.nodeType == 1 && // 1 means not just text or scaffolding. ( begin = lip.innerHTML.indexOf( pre )) != -1 ) { var test = unescape( lip.innerHTML.substring( begin + pre.length )); var atest = test.match( "[\./\\w]+" ); if( atest != null && atest[0] == val ) { //lip.firstChild.style.listStyle = "none inside disc"; Doesn't work. lip.firstChild.style.textDecoration="underline"; } } // Node type is 1 and prefix matches. } // Iterate through child nodes. } // cook != null. ref.style.display = "block"; } // Open closed menu. } // Found the dropdown menu by its id (ref != null). } // Function
menu.js implements HTML-based floating popup menus. The main function is openMenu, which opens or closes a floating popup menu. This is called a dropdown and it typically does drop down from a top horizontal list (sometimes called "taskbar") but the menu’s actual layout is determined by CSS style. If an optional cookie argument is provided, that cookie will be examined to determine which, if any, of the items should be highlighted to indicate that it is the current value. The menu itself has no side effects. Any action in response to item selection is done in the list item itself, which is typically an anchor. In my portfolio pages this is used only for the text size menu. Each menu item is an anchor whose href invokes javascript:setSize with the corresponding size cookie arguments.
Toggle open/close a DropDown menu list and optionally (on open) underline the menu item that matches the given cookie’s current value. Normally, if the user selects an item from the menu, that same cookie is assigned the value (not displayed name) of that item although that is not something that this function does.
Returns: nothing.
Arguments:
focus.js var refCenterItem; // Substitute for centerItem argument when called on timeout. function centerItem( ref ) { var itemOffset; var windowHeight; if( ref == 0 ) // If called on timeout, which can't pass dynamic argument. ref = refCenterItem; { itemOffset = ref.offsetTop; if( window.innerHeight > 0 ) // NN or FF windowHeight = window.innerHeight; else if( document.compatMode == "CSS1Compat" ) // IE6 { if( document.documentElement.clientHeight > 0 ) windowHeight = document.documentElement.clientHeight; else return; } else if( document.body.clientHeight > 0 ) // IE5 windowHeight = document.body.clientHeight; else return; if( windowHeight < 50 || windowHeight > 999 ) return; // Something is wrong. Probably stupid IE. // alert( "itemOffset=" + itemOffset + "windowHeight=" + windowHeight ); window.scrollTo( 0, itemOffset - windowHeight / 2 ); } } function focusUrl( center ) { var ref; if( document.getElementById && ( ref = document.getElementById( location.hash.substring(1)))!= null) { location.hash=""; /* Without this, the history URL makes return from * jump to any link on the page appear to be another prev/next and we would * focus this again instead of letting the browser go back to the link's * position. */ ref.focus(); // For FF (IE does this automatically). if( center ) { /* NN/FF definitely don't need to delay but I'm not sure about Safari. if( window.innerHeight > 0 ) // NN/FF centerItem( ref ); else // IE */ { refCenterItem = ref; /* Indirect argument to centerItem to get around setTimeout's prohibition against dynamic arguments. */ setTimeout( "centerItem( 0 )", 200 ); } } } }
focus.js contains functions to improve the presentation of a web page when it is accessed by its URL with a fragment (bookmark). Often this occurs when the user has clicked a link in another page (perhaps in another website) without knowing precisely what the target will be. They arrive at the page but have no idea what part of the page is supposedly related to the launch anchor. If the fragment refers to an anchor, the browser may render it with a visible focus but not all do this in all circumstances and most other elements have no obvious visible focus. These functions vertically center the page on the target and highlight it. I use this most notably in my Site Map page to focus on the calling page’s anchor to reveal its site context.
Scroll the browser window to vertically center the given item.
ref, the one argument, is the item’s reference. If it is 0 then the globar var
refCenterItem is used instead. This alternate argument is used when this
function is called directly by timeout, which doesn’t allow a dynamic argument
to be passed. A literal 0 can be passed to tell us to use the alternate
argument. The normal argument is for caller convenience. Any caller can use the
alternate form.
itemOffset is the px distance of the item from the beginning of the document. The item could be placed at the top of the window simply by scrolling to itemOffset. To place it in the middle of the window, scroll to itemOffset plus 1/2 of the window height. NN/FF have consistently provided window height by window.innerHeight. IE6 uses document.documentElement.clientHeight while IE5 uses document.clientHeight. Just to be sure to confuse everyone, both IE6 and IE5 use document.clientWidth to indicate window width.
NN/FF and IE use ref.offsetTop as itemOffset. px height of document is document. height in NN/FF but document.body.scrollHeight in IE. Call this docHeight. px distance of the top of the window from the beginning of the document is window. pageYOffset in NN and FF but document.body.scrollTop in IE. Call this windowOffset. 0 means window is at beginning of document. This function does not currently use docHeight or windowOffset but they might be used in a more sophisticated item placement scheme.
If the page URL includes a fragment that references a actual item in the page then set the focus on the item and, optionally, scroll the browser window to put the item in the vertical center of the window. This was specifically designed for a site map where any page jumping to the site map passes a URL fragment to the anchor that references itself so that the anchor is highlighted and shown in balanced before/after context.
Normally called by body onLoad, e.g. <body onLoad="navOnLoad(); " >
IE doesn’t actually need focus because it automatically focuses on the URL fragment. FF needs this.
This calls the general function centerItem, which scrolls the window. IE
prematurely triggers onLoad and certain functions, such as scrolling don’t work
for a while. There is no way to know when IE is actually ready, so a fairly
long delay is required. If the browser is IE, centerItem is called after a
200msec delay.
nav.js function navOnLoad() { var ref; var hash; var althash; if( document.getElementById ) { if(( ref = document.getElementById( "aValidHtml" ))) ref.href = "http://validator.w3.org/check?uri=" + document.URL; if(( ref = document.getElementById( "aValidCss" ))) ref.href = "http://jigsaw.w3.org/css-validator/validator?uri=" + document.URL; switch( location.hash ) { case "#xnavTHP": hash = "navTHP"; althash = "navTHN"; break; case "#xnavTHN": hash = "navTHN"; althash = "navTHP"; break; case "#xnavTLP": hash = "navTLP"; althash = "navTLN"; break; case "#xnavTLN": hash = "navTLN"; althash = "navTLP"; break; default: return; } if(( ref = document.getElementById( hash ))!= null || ( ref = document.getElementById( althash ))!= null ) { ref.focus(); location.hash=""; } } }
nav.js contains the function navOnLoad, which solves one general problem and supports one special navigation operation that I have incorporated into my portfolio website. The general problem is that when the w3.org html and css validation websites are called to validate a web page, the absolute URL of the page is passed as a fragment in the URL of the validators. This is inconvenient when pages are developed in one venue but publicized in another. This function modifies the page’s validation anchors as it loads so that they always contain the correct absolute URL of the page, regardless of what that is.
The special navigation operation is to set the focus (and highlight) previous and next anchors in my taskbar, which is a horizontal list of anchors at the top of the page. This makes it possible for the visitor to click on one of these in one page and then simply press the Enter key to iterate through my pages in a predefined order, irrespective of browser history.
This is called by body onLoad to do two things:
1. Assign the document’s URL to validation anchors.
2. Focus on high/low prev/next if called from one of these anchor types.
e.g. <body onLoad="navOnLoad(); " >
A page that has validation anchors but not the prev/next anchors can either include this file and call navOnLoad or embed the URL anchor assignment code in a javascript at the end of the page.
If the browser was sent to this page by hi/lo previous/next then focus on the corresponding anchor for convenient paging through page collections. Note trailing underscore on source but leading on destination. If they were the same we wouldn’t need scripting but the task bar would be at the top of the browser instead of the titlebar (also it wouldn’t work in FF). The althash is used to reverse direction when a collection contains only two pages.
Without the final statement location.hash="";
, the history URL
would make return from jump to any link on the page appear to be another
prev/next and we would focus this again instead of letting the browser go back to
the link’s position.
area.js var refBox; var refMapDiv; // the div that contains the mapped image. var refMapImg; // The image. var refMap; // The HTML map element. var boxIdx = 0; var jumpHref; function initImageMapAreas() { refBox = new Array( boxCnt ); refBox[0] = document.getElementById( "areaBox0" ); for( idx = 1 ; idx < boxCnt ; idx++ ) { refBox[idx] = document.getElementById("areaBox0").cloneNode( true ); refBox[idx].id = "areaBox" + idx; document.body.appendChild( refBox[idx] ); } refMapDiv = document.getElementById( "mapDivId" ); refMapImg = document.getElementById( "mapImgId" ); refMap = document.getElementById( "mapId" ); } function hideShowAreas() { while( boxIdx > 0 ) refBox[ --boxIdx ].style.display = "none"; } function jumpBox() { var boxAhref = jumpHref.split( "#" ); if( boxAhref.length == 1 ) location.href = jumpHref; // Target URL doesn't include an anchor point. else { var locAhref = location.href.split( "#" ); if( boxAhref[0] == locAhref[0]) location.hash = boxAhref[1]; else location.href = jumpHref; } } function showAreaByRef( aref ) { var posLeft = -refMapDiv.scrollLeft; var posTop = -refMapDiv.scrollTop; for( var ref = refMapImg ; ref != document.body ; ref = ref.offsetParent ) { posLeft += ref.offsetLeft; posTop += ref.offsetTop; } var coords = aref.coords.split( "," ); window.status = aref.title; jumpHref = aref.href; refBox[ boxIdx ].title = aref.title; refBox[boxIdx].style.display = "inline"; refBox[boxIdx].style.left = posLeft + parseInt( coords[0]) + "px"; refBox[boxIdx].style.top = posTop + parseInt( coords[1]) + "px"; refBox[boxIdx].style.height = parseInt( coords[3]) - parseInt( coords[1]) + "px"; refBox[boxIdx].style.width = parseInt( coords[2]) - parseInt( coords[0]) + "px"; boxIdx++; } function showAreaById( id ) { hideShowAreas(); showAreaByRef( document.getElementById( id )); } function showRelArea( aref ) { hideShowAreas(); for( var idx = 0 ; boxIdx < boxCnt && idx < refMap.areas.length ; idx++ ) { if( refMap.areas[ idx ].href == aref.href ) showAreaByRef( refMap.areas[ idx ]); } }
area.js provides improves image map with bidirectionally linked overlay boxes. These work two ways. The direct way draws a yellow border (box) around a defined area of an image map when the mouse rolls over it. The indirect way draws borders around as many as boxCnt areas that have the same area href as an anchor (not area) that the mouse hovers over. If JavaScript is disabled the image map degrades to standard. Web Design Image Map contains a detailed description.
The HTML file must define the following variables. boxCnt is defined in a script block. The others are defined in HTML. areaBox0 is class "AreaBox" defined in tech.css.
Jump to the href of the AREA under the mouse. Although this is triggered by the refBox over the AREA, neither the AREA nor the refBox directly provides the href. This is provided through the global jumpHref, which showAreaByRef copies from the AREA at the AREA's onMouseOver. For all browsers except Firefox, we would only have to assign location.href to successfully make the jump. Firefox stops playing animated GIFs (like my anchor hover) when this means is used to jump to a local anchor. All of the code here serves only one purpose-- to work around the FF bug. If the base of the target URL is the same as the current page then location.hash is assigned instead of the full href. The FF bug does not occur in this case.
Draw the next available area box around the given (by reference) map AREA. This may be called when the mouse is over the AREA itself or when the mouse is over a remote tag (normally an A with a matching href). The one argument aref is a reference to the object.
This ignores whether called in response to mouse over the AREA or over a remote tag. The only real differences are whether the jumpHref assignment made here will be subsequently used (it will be only if AREA) and the assignment of the window.status, which is not useful when over a remote tag.
The statement jumpHref = aref.href;
copies href from the
underlying area to global jumpHref. Unlike other attributes that are copied
from the AREA to the refBox object, this doesn’t exist when the refBox object
is an IMG instead of an A. Fortunately, also unlike the other elements, only
one href is needed. When it is assigned by AREA mouseOver, it defines the jump
target. That it is reassigned at additional refBox activations is irrelevant
for two reasons. One is that multiple refBoxs are active only due to mouseOver
another element (normally an A) in which case the AREA href wouldn’t be used.
The other reason is that all active refBoxs would have the same href anyway
(and it would be the same as the other element) since that is the tie that
binds them all in the first place.
Front end over showAreaByRef to simplify AREA mouseOver. This clears all highlight boxes before making the new one in order to clear the previous AREA rollover. This could have been cleared at onMouseOut of the box (image or anchor) but this event is missed so often (as warned by literature) that it can’t be relied on.
Called by remote tag (normally A) at onMouseOver to draw boxes around all AREA elements that have the same href. Although this calls showAreaByRef, which assigns jumpHref for jumping to the href of the AREA, this value will not be used because any click at this time will be handled by the remote tag’s onClick. However, since the AREAs are selected because they have the same href as the remote tag, clicking on this tag has the same effect as clicking on any one of the matching AREAs.
infbtn.js lockText = "locked"; unlockText = "unlocked"; /* ----------- PAGE STATIC DATA ------------------ */ /* Page static data initialized when the browser * loads a page that includes this file. */ ( info = new Image( 20, 20 )).src = "info.gif"; ( unlock = new Image( 20, 20 )).src = "unlock.gif"; ( lock = new Image( 20, 20 )).src = "lock.gif"; var cookies; // Holds all of the document's cookies. var infoContainer; function toggleLock( btnId ) { btnRef = document.getElementById( btnId ); if( btnRef.alt == lockText ) { btnRef.alt = unlockText; //btnRef.title = unlockText; btnRef.src = unlock.src; } else { btnRef.alt = lockText; //btnRef.title = lockText; btnRef.src = lock.src; } } function showInfo( btnId, infoId ) { infoRef = document.getElementById( infoId ); if( infoRef.style.display != "inline" ) { infoRef.style.display = "inline"; (document.getElementById( btnId )).src = unlock.src; /* IE doesn't erase the previous display when reformatting * to display the newly exposed info block. Changing any * property of the container tricks IE into repainting. */ infoContainer.style.display = "none"; infoContainer.style.display = "block"; } } function hideInfo( btnId, infoId ) { if( ( infoRef = document.getElementById( infoId )).display != "none" && ( btnRef = document.getElementById( btnId )).alt != "locked" ) { infoRef.style.display = "none"; btnRef.src = info.src; /* Trick IE into removing residuals. */ infoContainer.style.display = "none"; infoContainer.style.display = "block"; } } function readCookie( name ) { cookies = document.cookie; // Declare variable to set the "name=" value. var start = cookies.indexOf(name + "="); // Get the index if the cookie name is found. if (start == -1) return null; // Get the first character of the cookie. start = cookies.indexOf("=", start) + 1; // Read to the end of the cookie. var end = cookies.indexOf(";", start); if (end == -1) end = cookies.length; // Get the cookie value, reversing the escaped format by // using the unescape method. var value = unescape(cookies.substring(start, end)); if( value == null ) return null; else return value; } function toggleInfoCookie() { var exp = new Date(); var expiration = exp.getTime() + 315360000000; // 10*365*24*60*60*1000 = 3.1536e11 = 10 years. exp.setTime( expiration ); val = readCookie( siteCookie ); document.cookie = siteCookie + "=" + ( val == "hide" ? "show" : "hide" ) + "; expires=" + exp.toGMTString(); location.reload( false ); } /**** Automatic statements *******/ var infCookie = readCookie( siteCookie ); if( infCookie == "hide" ) document.write( '<link rel="stylesheet" type="text/css" href="infhide.css" >');
infbtn.js implements floating lockable tool tips. Web Design Info Buttons contains a detailed description.
The entire site cookie is used for controlling whether all info blocks are
displayed or only as selected. Each page that includes this script file must
define the siteCookie name before including this script file. e.g.
siteCookie = "cdNextInfo";
Each info button img has the following event responses. btnId is the id of the button img and infoId is the id of the info block span. The btnId name is typically the same as the infoId name with a "b" prefix, e.g. "btextInfo" with "textInfo", although this is not required.
This is called by each info button’s onClick. It toggles the button’s (img) state between "locked" and "unlocked" but does nothing to the span’s display property. This function never changes the button to its third state "i", which can exist only when the mouse is not over the button, in which case there can be no onClick. The three button states are represented by the "i", open lock, and closed lock images. The button’s alt attribute toggles between "locked" and "unlocked" but has no state corresponding to the "i" img. No other functions change the button’s alt. The image src is toggled to change the appearence between a closed and an open lock. The img element’s alt is toggled between "locked" and "unlocked". The main purpose for these values is to tell the button’s state for internal purposes but they also serve the normal visible purpose of alt as well.
This could change the title to reflect lock state but it is actually better to have no title because the tool tip just interferes with reading the info text and just says the same thing repeatedly. In fact, to prevent IE from displaying the alt text as tool tip, the title is explicitly nulled in every info button instance.
This is called by each info button’s onMouseOver. ShowInfo does nothing if the block is already displayed, regardless of the button state, although this must be "locked" if the block is already displayed at onMouseOver. However, if the block is hidden, its display property is changed to "inline" and the button img src is changed to "i" (open lock). The button’s alt must be "unlocked" and this is not changed.
To determine whether the block is already displayed, test for !=
"inline"
instead of == "none"
to avoid the following
problem; if CSS display:none
has established the element’s display
mode, JavaScript test for == "none"
fails. However, if JS assigns
"none" then the test doesn’t fail. To make the element immediately responsive
as soon as the page loads, test for != "inline"
, which fails
regardless of how "none" was assigned.
This is called by each info button’s onMouseOut. It hides the span only if it is not already hidden and the button is not "locked". If both of these conditions are met, the span’s display is changed to "none" and the button’s img src to "i". The button’s alt must be "unlocked" and this doesn’t need to change.
Get a cookie’s value by searching for the name of the cookie.
Toggle siteCookie’s value between "hide" and "show". This affects all pages of the web site that contain info buttons.
The statements at the end of infoBtn.js are not enclosed in functions and, therefore, execute automatically when the browser reads the file.
var infCookie = readCookie( siteCookie );
reads the initial cookie
value to determine whether to display all info contents (and not display the
info buttons).
The final statement if( infCookie == "hide" ) document.write( '<link
rel="stylesheet" type="text/css" href="infhide.css" >');
must be
located at the end of a script block. It will interfere with any subsequent
script code in the same block because it writes HTML code immediately into the
page. It loads infhide.css when the page loads if the site cookie is "hide".
This and the img.info display:none
and span.info display:
inline
declarations in tech.css constrain info button pages to default
to showing all info if JS is disabled or there is no cookie. A page could
provide the alternative "hide" default by redefining the following:
img.info display: inline
span.info display: none
.InfoControl img background-color: #c5d8ee (lt. blue)
if infCookie == "show" write infshow. css