Stencil Javascripts

Stencil is primarily a C++ template engine, however complex web-based applications often require that some work be done on the client-side. So, Stencil also includes a set of javascripts that I have found useful over the years.


navigate.js

navigate.js provides 2 methods that can manipulate form data and activate the form. The setValues() function sets the values of variables in the form and the navigate() function sets the form's action and target and submits the form.

These functions solve an obscure but remarkably common problem.

Let's say you have an addressbook application. One page lists people's names and clicking on a name links to another page with the person's address, city, state and phone number. The page could be implemented like this:

<html>
<body>
        <ul>
        <li><a href="detail.cgi?name=david">David</a></li>
        <li><a href="detail.cgi?name=mark">Mark</a></li>
        <li><a href="detail.cgi?name=nick">Nick</a></li>
        <li><a href="detail.cgi?name=mike">Mike</a></li>
        </ul>
</body>
</html>

But detail.cgi might require that data be posted to it, or you might want to post data to it to prevent the detail pages from being bookmarked. In that case, the page would need to look like this:

<html>
<body>
        <ul>
        <form action="detail.cgi" method="post">
        <li><input type="submit" name="name" value="david"></li>
        </form>
        <form action="detail.cgi" method="post">
        <li><input type="submit" name="name" value="mark"></li>
        </form>
        <form action="detail.cgi" method="post">
        <li><input type="submit" name="name" value="nick"></li>
        </form>
        <form action="detail.cgi" method="post">
        <li><input type="submit" name="name" value="mike"></li>
        </form>
        </ul>
</body>
</html>

But that page is really ugly and duplicates an almost identical form over and over. navigate.js allows you to define the form once and use hyperlinks instead of submit buttons. In this example, when a hyperlink is clicked, it sets the "name" input of the form to the appropriate name, sets the action of the form to "detail.cgi", sets the target of the form to "_self" and submits the form.

Though not illustrated in this example, you could submit the same form to different CGI's by or target different frames by passing different arguments to navigate() in each link.

<html>
<head>
        <script language="Javascript" src="navigate.js"></script>
</head>
<body>
        <form name="detail">
                <input type="hidden" name="name" value="">
        </form>
        <ul>
        <li><a href="javascript: setValues(document.detail,'name','david');
                        navigate(document.detail,'detail,cgi','_self');"
                        onmouseover="javascript:window.status='David';">David</a></li>
        <li><a href="javascript: setValues(document.detail,'name','mark');
                        navigate(document.detail,'detail,cgi','_self');"
                        onmouseover="javascript:window.status='Mark';">Mark</a></li>
        <li><a href="javascript: setValues(document.detail,'name','nick');
                        navigate(document.detail,'detail,cgi','_self');"
                        onmouseover="javascript:window.status='Nick';">Nick</a></li>
        <li><a href="javascript: setValues(document.detail,'name','mike');
                        navigate(document.detail,'detail,cgi','_self');"
                        onmouseover="javascript:window.status='Mike';">Mike</a></li>
        </ul>
</body>
</html>

Here is the javascript itself. See the usage notes below for more information.

// Copyright (c) 1999-2000 David Muse
// See the COPYING file for more information


// Usage Example:

// <form name="navigate">
// <input type="hidden" name="variable1">
// <input type="hidden" name="variable2">
// </form>

// <a href="javascript: 
//      setValues(document.navigate, 
//              'variable1','value1','variable2','value2');
//      navigate(document.navigate,'nextpage.cgi','_top');">Next Page</a>


function        setValues() {
        for (var i=1; i<arguments.length; i=i+2) {
                eval('arguments[0].elements.'+arguments[i]+
                        '.value=arguments[i+1];');
        }
};

function        navigate(whichform,action,target) {
        whichform.target=target;
        whichform.action=action;
        whichform.submit();
};

newwindow.js

Sometimes links on web sites pop up new browser windows. Many people run their browsers maximized. When the new browser window pops up, it also pops up maximized. The user isn't necessarily aware that a new window had popped up and is confused when the back button is greyed out. It would be nice if the new browser window had been sized and positioned such that it were obvious that a new browser window had been created.

newwindow.js provides a newWindow() function which creates a new browser window which is intelligently sized and positioned. The top, left corner of the new window is positioned 30 pixels to the right and 30 pixels down from the parent window. The new browser window is created with the same size as the parent window unless the parent window is within 70% of the screen size (ie. it's "nearly" maximized), in which case, the size of new browser window is reduced by 20%.

See the usage notes below for more information.

// Copyright (c) 1999-2000 David Muse
// See the COPYING file for more information

function        newWindow(name,url,replace) {

        // Usage example:

        // <a href="javascript:  
        //      newWindow('popup','http://www.firstworks.com',true);">
        //              firstworks</a>


        var     screenheight=window.screen.availHeight;
        var     screenwidth=window.screen.availWidth;

        var     windowheight=top.outerHeight;
        var     windowwidth=top.outerWidth;

        var     newwindowtop=window.screenY+30;
        var     newwindowleft=window.screenX+30;

        // If top window is close to maximized, pop up new window 
        // smaller than the new one.
        // If top window is not close to maximized, pop up new window
        // as the same size.

        var     newwindowheight=windowheight;
        var     newwindowwidth=windowwidth;

        if ((windowheight>(screenheight*0.70)) &&
                (windowwidth>(screenwidth*0.70))) {

                newwindowwidth=parseInt(windowwidth*0.80);
                newwindowheight=parseInt(windowheight*0.80);
        }

        window.open(url,name,"outerHeight="+newwindowheight+
                ",outerWidth="+newwindowwidth+
                ",screeny="+newwindowtop+",screenx="+newwindowleft+
                "directories=yes,location=yes,menubar=yes,"+
                "personalbar=yes,resizable=yes,scrollbars=yes,"+
                "status=yes,toolbar=yes",replace);
}

norightclick.js

Browser users commonly download images or movies from web sites by right-clicking on the image or movie link and selecting Download Link (or the equivalent). One way to deter this is to disable right-clicking.

Including norightclick.js in your page prevents most browsers from displaying the right-click menu when a user right-clicks on the page.

Users can still "view source", search through the source and copy and paste image or movie URL's but that's much more work than just right clicking and is enough of a deterrent for most users.

See the usage notes below for more information.

// Copyright (c) 2002 David Muse
// See the COPYING file for more information

// Usage example:

// To disable right clicking (in most browsers), include this script in your
// page as follows:

// <script language="Javascript" src="norightclick.js"></script>

function        catchClicks(event) {
        var     button=1;
        if (indexOf(navigator.appName=='Netscape')!=-1 ||
                indexOf(navigator.appName=='Konqueror')!=-1 ||
                indexOf(navigator.appName=='Opera')!=-1) {
                button=event.which;
        } else if (indexOf(navigator.appName=='Internet Explorer')!=-1) {
                button=event.button;
        }
        if (button==2 || button==3) {
                return false;
        }
        return true;
}

// catch clicks at the document level
document.onmousedown=catchClicks;
document.onmouseclick=catchClicks;
document.onmouseup=catchClicks;

// if the document has layers, let the top level window catch the click events
if (document.layers) {
        window.captureEvents(Event.MOUSEDOWN);
        window.captureEvents(Event.MOUSECLICK);
        window.captureEvents(Event.MOUSEUP);
}

// catch clicks at the window level
window.onmousedown=catchClicks;
window.onmouseclick=catchClicks;
window.onmouseup=catchClicks;

safeunescape.js

Javascript's built-in unescape() function takes a URL-encoded string and converts it to plain text. For example: unescape("It%27s%20me%21") returns "It's me!". However, spaces aren't always encoded using %20, usually, browsers encode them using +'s. So, unescape("It%27s+me%21") returns "It's+me!". The safeUnescape() function handle's +'s "properly". So safeUnescape("It%27s+me%21") returns "It's me!".

// Copyright (c) 2002 David Muse
// See the COPYING file for more information


function safeUnescape(string) {
        var newstring="";
        var character;
        for (var i=0; i<string.length; i++) {
                character=string.substring(i,i+1);
                if (character=="+") {
                        newstring=newstring+"%20";
                } else {
                        newstring=newstring+character;
                }
        }
        return unescape(newstring);
}

textfilter.js

Sometimes it's nice to reformat the contents of text inputs in the browser itself, before the form is submitted. For instance, you might want to force an entry to upper or lower case, or force dashes into the right positions in a phone number or social security number.

textfilter.js provides the textFilter() function which does just that. Setting the onblur attribute of an input to call textFilter() causes textFilter() to reformat the contents of the text input when the user moves on to the next input or submits the form.

See the usage notes below for more information.

// Copyright (c) 1999-2000 David Muse
// See the COPYING file for more information

function textFilter(whichelement,filter,nulls) {

        // Usage Example:

        // <form name="personalinfo">
        // <input type="text" name="name" size="40" 
        //              onblur="textFilter(this,'upper',true);">
        // <input type="text" name="phone" size="12"
        //              onblur="textFilter(this,'phone',true);">
        // <input type="text" name="ssn" size="11"
        //              onblur="textFilter(this,'ssn',true);">
        // <input type="text" name="zip" size="5"
        //              onblur="textFilter(this,'integer',true);">
        // </form>

        // The filter argument may be one of the following:
        //      integer
        //      float
        //      ssn
        //      phone
        //      upper
        //      lower

        // The nulls argument works for integers or floats.  A 'false' 
        // converts blank text inputs to 0's, a 'true' leaves them blank.


        var     textstring=whichelement.value;
        var     newstring='';
        var     ch='';


        // first remove all possible offending characters
        if (filter=='float' || filter=='integer' || filter=='ssn' ||
                filter=='phone') {
                for (var i=0; i<textstring.length; i++) {
                        ch=textstring.substr(i,1);
                        if (ch=='1' || ch=='2' || ch=='3' || ch=='4' ||
                                ch=='5' || ch=='6' || ch=='7' ||
                                ch=='8' || ch=='9' || ch=='0' ||
                                ((ch=='.' || ch=='-') &&
                                (filter=='float' ||
                                filter=='integer')) ||
                                ((ch=='e' || ch=='x' || ch=='t' ||
                                ch=='E' || ch=='X' || ch=='T') &&
                                (filter=='phone'))) {
                                newstring=newstring+ch;
                        }
                }
        }
        textstring=newstring;
        newstring='';

        // format the string
        if (filter=='float' || filter=='integer') {
                var     nodecimal=true;
                var     roundval=1;
                for (var i=0; i<textstring.length; i++) {
                        ch=textstring.substr(i,1);
                        if (ch=='1' || ch=='2' || ch=='3' || ch=='4' ||
                                ch=='5' || ch=='6' || ch=='7' ||
                                ch=='8' || ch=='9' || ch=='0' ||
                                (ch=='-' && i==0)) {
                                if (ch=='-') {
                                        roundval=-1;
                                }
                                newstring=newstring+ch;
                        } else if (ch=='.') {
                                if (filter=='float') {
                                        if (nodecimal) {
                                                newstring=newstring+ch;
                                        }
                                        nodecimal=false;
                                } else if (filter=='integer') {
                                        ch=textstring.substr(i+1,1);
                                        if (parseInt(ch)>=5) {
                                                newstring=(parseInt(newstring)+
                                                        roundval).toString();
                                        }
                                        break;
                                }
                        }
                }
                if (newstring.length==0 && !nulls) {
                        newstring='0';
                }
        } else if (filter=='ssn') {
                for (var i=0; i<textstring.length; i++) {
                        ch=textstring.substr(i,1);
                        if (i==3 || i==5) {
                                newstring=newstring+'-';
                        }
                        newstring=newstring+ch;
                }
        } else if (filter=='phone') {
                var     areacode=false;
                var     prefix=false;

                var     length;
                var     extindex=textstring.indexOf('ext');
                if (extindex==-1) {
                        extindex=textstring.indexOf('EXT');
                }
                if (extindex==-1) {
                        length=textstring.length;
                } else {
                        length=extindex;
                }
                if (length>7) {
                        areacode=true;
                }
                if ((length==8 &&
                        (textstring.substr(0,1)=='0' ||
                        textstring.substr(0,1)=='1'))) {
                        areacode=false;
                }

                for (var i=0; i<textstring.length; i++) {
                        ch=textstring.substr(i,1);
                        if (i==0 && (ch=='0' || ch=='1')) {
                                newstring=newstring+ch+'-';
                                prefix=true;
                        } else if ((i==2 && !prefix) ||
                                        (i==3 && prefix)) {
                                newstring=newstring+ch+'-';
                        } else if (areacode &&
                                        ((i==5 && !prefix) ||
                                        (i==6 && prefix))) {
                                newstring=newstring+ch+'-';
                        } else if (ch=='e' || ch=='E') {
                                newstring=newstring+' '+ch;
                        } else if (ch=='t' || ch=='T') {
                                newstring=newstring+ch+' ';
                        } else {
                                newstring=newstring+ch;
                        }
                }
        } else if (filter=='lower') {
                whichelement.value=whichelement.value.toLowerCase();
                return;
        } else if (filter=='upper') {
                whichelement.value=whichelement.value.toUpperCase();
                return;
        }

        // set the text value
        whichelement.value=newstring;
}

cookies.js

cookies.js provides functions for getting, setting and deleting cookies. It's usage should be fairly self-explanatory.

// Copyright (c) 2002 David Muse
// See the COPYING file for more information


function getCookie(name) {
        var     cookies=document.cookie.split("; ");
        for (var i=0; i<cookies.length; i++) {
                var     cookie=cookies[i].split("=");
                if (cookie[0]==name) {
                        return safeUnescape(cookie[1]);
                }
        }
        return null;
}

function setCookie(name, value, expires, path, domain, secure) {
        var cookie=name+"="+escape(value);
        if (expires) {
                cookie+="; expires="+expires.toGMTString();
        }
        if (path) {
                cookie+="; path="+path;
        }
        if (domain) {
                cookie+="; domain="+domain;
        }
        if (secure) {
                cookie+="; secure";
        }
        document.cookie=cookie;
}

function deleteCookie(name) {
        setCookie(name,'null',new Date(0),null,null,true);
}