<!--

/*  *******************************************************************  */
/*  For inclusion as first script in the HEAD of every displayable page  */
/*  *******************************************************************  */

/*  This script is common to all pages at the site. Different types of page 
    follow with different other scripts.  */

/*  *************  */
/*  Configuration  */

/*  The site presents documents in some context, most notably with a banner 
    and a table of contents (TOC), without needing that the documents 
    themselves be prepared with any knowledge of this. 

    When a document page is loaded directly, its DOCUMENT.JS runs in the 
    top window, constructs a frameset, substitutes it for the document's 
    body and puts the original body into one of the frames. 
    
    So that scripts running in the frames can identify that they are in the 
    expected frameset, we give the top window a particular name.  */

var sTopWindow = "Frameset window for Geoff Chappell site";

/*  The first FRAMESET divides the top window into rows. The first row is 
    one FRAME, specifically for a banner. The second row, typically most of 
    the window, is in turn a FRAMSET, to define columns. The first, 
    typically narrower, column is a FRAME for a table of contents (TOC). The 
    second, which is the main presentation area, is a FRAME for the site's 
    many documents. 

    So that scripts running in the frames can identify that they are in the 
    correct frame, we give each FRAME a name. 

    Note for the future: if any of these names can be depended on without 
    checking that they are inside the top window, then these too ought to be 
    particular to the site.  */

var sBannerFrame = "banner";
var sTocFrame = "toc";
var sDocFrame = "doc";

/*  The banner is always loaded from the one file for the whole site.  */

var sBannerPathname = "/banner.htm";

/*  The site is presented as a collection of subwebs. Though each actually 
    may be a subweb in the sense of Microsoft's Expression Studio (which is 
    used as the word processor for writing the content), the sense intended 
    here is that each has its own TOC. The following is the definitive list 
    of subwebs. All the paths must be absolute.  */

var asSubwebPaths = new Array (
    "/notes",
    "/studies/windows/km",
    "/studies/windows/win32",
    "/studies/windows/shell",
    "/studies/windows/ie",
    "/studies/msvc"
);

/*  Each subweb's TOC has the same filename.  */

var sTocFilename = "toc.htm";

/*  Filename for default document on any path  */

var sDefaultFilename = "index.htm"

/*  Master list of search-string arguments  */

var sBookmarkArgumentName = "bm";       // obsolete - see VIEWER.JS
var sDocumentArgumentName = "doc";      // obsolete - see VIEWER.JS
var sTocScrollArgumentName = "ts";
var sTocWidthArgumentName = "tw";
var sTocExpansionArgumentName = "tx";

/*  The site must not depend on scripts to run. To compensate for 
    navigational support that's missed if scripts don't run, many pages are 
    prepared with at least some content (usually by way of a design-time 
    inclusion) that is meant to be seen only if scripts don't run. All such 
    content has the following class name. If scripts do run, they hide all 
    DOM nodes that have this class.  */

var sNoScriptClass = "NoScript";

/*  In an ideal world, re-presentation in the frameset would be unaffected 
    by a change of hostname. However, if a page gets reproduced at another 
    site, it will likely not be just the hostname that changes but the 
    pathname also. Determination of which subweb a page belongs to then 
    becomes unreliable. This is particularly a problem with caching by 
    search engines. If only for now, we just don't run any scripts 
    non-trivially unless we recognise the hostname.  */

var aRecognisedHostNames = new Array (
    /(?:.+\.)*geoffchappell(?:\..+)*/i
);

/*  For one reason or another, it may sometimes be wanted to block access 
    from certain other sites. For each, we redirect to an explanatory note.  */

function BadReferrer (Source, Target)
{
    this.Source = Source;
    this.Target = Target;
}

var aBadReferrers = new Array (
    new BadReferrer (
        /(?:.+\.)*msfn.org(?:\/.*)*/i, 
        "/redirect/msfn.htm"),
    new BadReferrer (
        /(?:.+\.)*experts-exchange.com(?:\/.*)*/i, 
        "/redirect/experts-exchange.htm")
);

/*  ******************  */
/*  Browser Variations  */

/*  Some work must be done differently for different browsers according to 
    what functionality appears to be available. The overall pattern here is 
    that, rightly or wrongly, the way to do the work in Internet Explorer is 
    known and the standards are what we fall back to.  */

function RegisterEventHandler (Node, Event, Handler)
{
    if (Node.attachEvent != null) {
        Node.attachEvent ("on" + Event, Handler);
    }
    else {
        Node.addEventListener (Event, Handler, false);
    }
}

function EnsureEvent (Event)
{
    return Event == null ? window.event : Event;
}

function GetEventSource (Event)
{
    var src = Event.srcElement;
    return src != null ? src : Event.target;
}

function SetEventDone (Event)
{
    if (Event.stopPropagation == null) {
        Event.cancelBubble = true;
        Event.returnValue = false;
    }
    else {
        Event.stopPropagation ();
        Event.preventDefault ();
    }
}

function GetInnerText (Element)
{
    var text = Element.innerText;
    return text != null ? text : Element.textContent;
}

function AppendRule (Sheet, Selector, Style)
{
    if (Sheet.addRule != null) {
        var numrules = Sheet.rules.length;
        Sheet.addRule (Selector, Style);
        return numrules;
    }
    else {
        var rule = Selector + "{" + Style + "}";
        var numrules = Sheet.cssRules.length;
        return Sheet.insertRule (rule, numrules);
    }
}

function RemoveRule (Sheet, RuleNumber)
{
    if (Sheet.removeRule != null) {
        Sheet.removeRule (RuleNumber);
    }
    else {
        Sheet.deleteRule (RuleNumber);
    }
}

/*  There's no variation for the following two helper functions, but they 
    are as well placed with the preceding.  */

function GetFirstStyleSheet ()
{
    var sheets = window.document.styleSheets;
    return sheets.length != 0 ? sheets [0] : null;
}

function GetLastStyleSheet ()
{
    var sheets = window.document.styleSheets;
    var numsheets = sheets.length;
    return numsheets != 0 ? sheets [numsheets - 1] : null;
}

/*  **************  */
/*  Script Support  */

/*  Whatever the browser, there is minimal functionality that we assume and 
    some more functionality that we explicitly require. The practical effect 
    for Internet Explorer is to assume version 5.0 or higher and require 
    version 5.5 or higher.  */

function IsLowScriptSupport ()
{
    if (Array.prototype.push == null) return true;
    if (Array.prototype.splice == null) return true;
    return false;
}

/*  For reasons noted above, we let the scripts run only for known hosts.  */

function IsBadHostName ()
{
    var hostname = window.location.hostname;
    var count = aRecognisedHostNames.length;
    for (var i = 0; i < count; i ++) {
        var recog = aRecognisedHostNames [i];
        if (recog.test (hostname)) return false;
    }
    return true;
}

/*  If scripts run, we'll hide anything that is meant to be seen only if 
    scripts don't run. It is here assumed that rules for the NoScript class 
    are in MASTER.CSS only, such that we can override by appending to the 
    first stylesheet.  */

function HideNoScriptBlocks ()
{
    var sheet = GetFirstStyleSheet ();
    if (sheet != null) AppendRule (sheet, "." + sNoScriptClass, "display:none;");
}

/*  ********  */
/*  Frameset  */

function GetFrame (Name)
{
    /*  If the top window does not have the name we expect for our frameset, 
        then it can't have any of our frames underneath.  */

    var top = window.top;
    if (top.name != sTopWindow) return null;

    /*  The frame that we want must be among the top window's frames and is 
        specifically the first that has the wanted name. 

        Internet Explorer's Restricted Zone disables frames in a very 
        curious way. It allows frames to exist, and even renders them as 
        empty containers, but it throws an exception on an attempt to access 
        a frame's window object.  */

    var e;
    try {
        var frames = top.frames;
        var numframes = frames.length;
        for (var n = 0; n < numframes; n ++) {
            var frame = frames [n];
            if (frame.name == Name) return frame;
        }
    }
    catch (e) {
    }

    /*  Though the frames collection for a window object is supported by 
        apparently all browsers, it seems to have no definition in any 
        standard. So, if a frame isn't found through the frames collection, 
        do not be too quick to give up. Seek the frame's DOM node and 
        thence its contentWindow - not that this is in any standard, 
        either.  */

    try {
        var frames = top.document.getElementsByTagName ("FRAME");
        var numframes = frames.length;
        for (var n = 0; n < numframes; n ++) {
            var frame = frames [n];
            if (frame.name == Name) return frame.contentWindow;
        }
    }
    catch (e) {
    }

    return null;
}

function IsInFrameset (Name)
{
    return window.name == Name && GetFrame (Name) == window;
}

/*  ************  */
/*  Path Helpers  */

function PathAppend (Path, Name)
{
    if (Path.charAt (Path.length - 1) == "/") {
        return Path + (Name.charAt (0) == "/" ? Name.substr (1) : Name);
    }
    else {
        return Path + (Name.charAt (0) == "/" ? Name : "/" + Name);
    }
}

/*  *******  */
/*  Subwebs  */

function GetSubwebPath (Pathname)
{
    /*  Default to the pathname from the top window.  */

    if (arguments.length < 1 || Pathname == null) {
        Pathname = window.top.location.pathname;
    }

    /*  Save our caller from having to worry whether the Pathname begins 
        with a slash.  */

    if (Pathname.charAt (0) != "/") Pathname = "/" + Pathname;

    /*  Find a subweb that begins the pathname, such that the two are the 
        same or (more generally) the pathname continues after a slash.  */

    var numsubwebs = asSubwebPaths.length;
    for (var n = 0; n < numsubwebs; n ++) {
        var subweb = asSubwebPaths [n];
        var cchsubweb = subweb.length;
        if (Pathname.substr (0, cchsubweb) == subweb) {
            if (Pathname.length == cchsubweb) return subweb;
            if (Pathname.charAt (cchsubweb) == "/") return subweb;
        }
    }
    return "/";
}

/*  ***************************  */
/*  URL Search String Arguments  */

/*  A search string may be appended to a page's URL to pass parameters to 
    the page. As parsed for the location.search property, it begins with a 
    question mark and is then followed by any number of arguments, 
    separated by & signs. Each argument has the form of a name and value, 
    separated at the first equals sign.  */

/*  A simple object to help manage search strings  */

function ParsedSearch (Search)
{
    this.Names = new Array ();
    this.Values = new Array ();

    if (arguments.length >= 1 && Search != null) {
        Search = Search.toString ();
        if (Search != "") {
            if (Search.charAt (0) == "?") Search = Search.substr (1);
            var args = Search.split ("&");
            var numargs = args.length;
            for (var n = 0; n < numargs; n ++) {
                var nv = args [n].split ("=", 3);
                if (nv.length == 2) {
                    var name = nv [0];
                    if (name != "") {
                        var value = nv [1];
                        if (value != "") {
                            this.Names.push (name);
                            this.Values.push (value);
                        }
                    }
                }
            }
        }
    }
}

/*  A method to look up a value by name  */

ParsedSearch.prototype.Get = function (Name)
{
    var numnames = this.Names.length;
    for (var n = 0; n < numnames; n ++) {
        if (this.Names [n] == Name) return this.Values [n];
    }
    return null;
};

/*  A method to add a value for a name or to change the value for an 
    existing name (returning the old value)  */

ParsedSearch.prototype.Add = function (Name, Value)
{
    var numnames = this.Names.length;
    for (var n = 0; n < numnames; n ++) {
        if (this.Names [n] == Name) {
            var oldval = this.Values [n];
            this.Values [n] = Value;
            return oldval;
        }
    }
    this.Names.push (Name);
    this.Values.push (Value);
    return null;
};

/*  A method to look up a value by name and then remove the pair (returning 
    the old value)  */

ParsedSearch.prototype.Delete = function (Name)
{
    var numnames = this.Names.length;
    for (var n = 0; n < numnames; n ++) {
        if (this.Names [n] == Name) {
            this.Names.splice (n, 1);
            return this.Values.splice (n, 1) [0];
        }
    }
    return null;
};

/*  A method to extract a search string from a ParsedSearch  */

ParsedSearch.prototype.toString = function ()
{
    var numnames = this.Names.length;
    if (numnames == 0) return "";
    var str = "?" + this.Names [0] + "=" + this.Values [0];
    for (var n = 1; n < numnames; n ++) {
        str += "&" + this.Names [n] + "=" + this.Values [n];
    }
    return str;
};

/*  ***************  */
/*  Local Locations  */

/*  An object for working with URLs whose protocol and hostname are assumed 
    from the top window  */

function LocalUrl (Pathname, Search, Hash)
{
    /*  An absolute pathname taken from the pathname member of a link will 
        typically (and apparently deliberately) not have a leading slash. If 
        absent, insert one so that the difference won't matter to us 
        afterwards and not to our callers at all.  */
    
    this.Pathname = Pathname.charAt (0) == "/" ? Pathname : "/" + Pathname;

    /*  The Search and Hash arguments are optional.  */

    this.Search = "";
    this.Hash = "";

    if (arguments.length >= 2) {
        if (Search != null) {

            /*  Allow that the search string need not actually be a string. 
                (The intended alternative is a ParsedSearch object.)  */

            Search = Search.toString ();

            /*  It seems that a search string from either a location object 
                or link will always start with a "?", if non-empty - but 
                check, inserting one if absent.  */

            if (Search != "") {
                this.Search = Search.charAt (0) == "?" ? Search : "?" + Search;
            }
        }
        if (arguments.length >= 3 && Hash != null) {

            /*  It seems that a hash from either a location object or link 
                will always start with a "#", if non-empty - but check, 
                inserting one if absent.  */

            if (Hash != "") {
                this.Hash = Hash.charAt (0) == "#" ? Hash : "#" + Hash;
            }
        }
    }
}

/*  A method to modify the search string to record the TOC state - this 
    calls a function in TOC.JS in the TOC frame  */

LocalUrl.prototype.LoadTocState = function ()
{
    this.Search = new ParsedSearch (this.Search);

    var toc = GetFrame (sTocFrame);
    if (toc != null) toc.ComposeTocArguments (this.Search, this.Pathname);

    window.top.ComposeTocArguments (this.Search, this.Pathname);
};

/*  A method to extract a full URL from a LocalUrl  */

LocalUrl.prototype.toString = function ()
{
    var location = window.top.location;
    return location.protocol + "//" + location.hostname + this.Pathname 
            + this.Search + this.Hash;
};

/*  ==============  */
/*  Internal Links  */

/*  Links within the site must preserve the TOC state. Call the following 
    only after establishing that the link is local.  */

function RedirectLocalLink (Link)
{
    var pathname = Link.pathname;
    if (pathname.indexOf ("/_") == -1) {
        var url = new LocalUrl (pathname, Link.search, Link.hash);
        url.LoadTocState ();
        Link.href = url;
        Link.target = "_top";
    }
}

/*  Most often best is to redirect a local link only when someone actually 
    does try to follow it. The following can be common code for an onclick 
    handler.  */

function RedirectClickedLink (Event)            // onclick 
{
    Event = EnsureEvent (Event);
    var hostname = window.top.location.hostname;
    for (var x = GetEventSource (Event); x != null; x = x.parentNode) {
        if (x.nodeName == "A") {
            if (x.hostname == hostname) RedirectLocalLink (x);
            break;
        }
    }
}

/*  ********************  */
/*  Block a Bad Referrer  */

function RedirectBadReferrer ()
{
    var ref = window.document.referrer;
    if (ref == null) return false;

    var count = aBadReferrers.length;
    for (var n = 0; n < count; n ++) {
        var bad = aBadReferrers [n];
        if (bad.Source.test (ref)) {
            window.top.location.replace (bad.Target);
            return true;
        }
    }
    return false;
}

/*  Copyright © 2007-2012. Geoff Chappell. All rights reserved.  */

//-->
