/*
    tabmanager.js - provides a simple API for tabs on a webpage
    Copyright (C) 2004 Guido Wesdorp
    email johnny@debris.demon.nl

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    The TabManager is a simple, fully automated tab manager, all you need to
    do is provide a bunch of HTML, instantiate the TabManager and use it
    (tell it which tabs to render).

    Usage (for a working example see 'test.html'):

      var tm = new TabManager();

      // This will get all top-level (non-text) nodes from the element with id
      // 'tabs', each of the elements is considered a tab. They must have an
      // id, that will be used as the tab id, and can have a 'tabname' 
      // attribute with the name that will be displayed (id is used as a 
      // fallback. The HTML in the page can optionally be parsed by a function
      // before it is displayed.
      var tabsel = document.getElementById('tabsel');
      tm.initialize(document, tabsel);
    
      // set up a set of static tabs, the ids passed in are the ids of the
      // top-level elements

      // add tab containing contents of element with id 'tab1' without parsing
      tm.addTab('tab1');

      // add content of 'tab2', parse it with function 'parseTab2'
      tm.addTab('tab2', parseTab2);

      // render the tabs
      tm.render();

      // unrender the tabs again, then render only tab1
      tm.unrender();
      tm.addTab('tab1');
      tm.render();

      Public roperties:
      
        TabManager:

          active_tabs
            an array containing the current active tabs

          current_active_tab_index
            the index (id attribute of the content element) of the tab that is
            currently displayed

          tabs
            a mapping (object, 'associative array') of tabid to Tab object

          container
            a reference to the element containing the tabs

          tabrow
            the element containing the tab labels (buttons)

          contentel
            the element containing the tab content

        TabManager:

          id
            the id of the tab, the id of the HTML node containing the tab's
            content is used for this

          name
            the 'tabname' attribute of the tab content node, is displayed in
            the tab button (label)

          doc
            the document containing the tab table

          tabmanager
            a reference to the TabManager that controls the tab

          template
            a reference to the (unparsed) template element containing the 
            tab's content

          rendered
            a reference to the (parsed if a prepareFunction was used) template
            content (without the help bit), if you want to modify the contents
            of an already rendered tab (so *not* from the prepareFunction but
            rather from an event handler or something) you will need to modify
            this attribute

          use_rendered
            whether to re-use the rendered template contents when the tab is
            shown after the first time it was rendered
      
      Public methods:

        TabManager:

          initialize(doc, tabsel)
            initialize the tab manager, must be called after instantiation
            doc is a reference to the document containing the tabsel, tabsel
            is a node containing a set of nodes that contain tab data, each
            tabsel must have an id, can have an attribute 'tabname' and should
            contain some content

          render()
            render the current active tabs

          unrender()
            unrender the current active tabs so a new set can be created

          activateTab(index, reload)
            show a tab, index is the id attribute of the tab element and reload
            is passed to Tab.getContents() (if called) to control caching
            (getContents() is only called when the tab is shown the first
            time after TabManager.rendered() is called, and the 'reload' arg
            can be used to cache *external* data to store in the tab (so to
            tell getContents() we want un-cached data between 
            TabManager.render() calls)
            activateTab() will not be of much interest to you if you use a
            simple setup, it's called when a tab button is clicked

          addTab(id, prepareFunction)
            show a tab (actually add it to the list of current tabs before
            calling TabManager.render()), id is the id attribute of the node
            containing the tab content, prepareFunction is an (optional) 
            function to parse the tab contents before it is displayed
            prepareFunction will be called with as arguments a reference
            to the tab, a clone of the tab contents (don't ever try to change
            anything in HTML stored on tab itself, always modify this clone!)
            and the 'reload' argument mentioned in the activeTab() bit above

          usedTabs()
            get a list of ids of the current used tabs

          reload()
            reload the contents of all the tabs, call any prepareFunctions()

      Tab:

        initialize(doc, tabmanager, templateel)
          initialize the tab, is called from the TabManager on object 
          instantiation (you should not have to instantiate Tab objects 
          yourself, TabManager will do that on initialization)

        beforeShow()
          hook that will be called just before the tab is displayed

        afterShow()
          hook that will be called just when the tab is displayed

        beforeHide()
          hook that will be called just before the tab is removed from view

        afterHide()
          hook that will be called just whenn the tab is removed from view

        reload()
          reload the tabs contents, calling prepareFunction() if registered

        displayHelp()
          is called when the user presses the help button, displays an optional
          piece of the tab's content that is a direct child of the tab node and
          has a 'type' attribute set to 'help' in a pop-up window
*/

var global = this;
global.tabmanager = new function tabmanager() {
    var tabmanager = this;

    //------------------------------------------------------------------------
    // TabManager class
    //------------------------------------------------------------------------
    function TabManager() {
        /* generates and manages the tabs */
    };

    this.TabManager = TabManager;

    TabManager.prototype.initialize = function(doc, tabsel) {
        /* load the contents of elid into the tabs */
        this.doc = doc;
        this.active_tabs = [];
        this.current_active_tab_index = null;
        var tc = this.tabscontainer = tabsel;
        // put all children of tc in a list
        var tabs = this.tabs = {};
        for (var i=0; i < tc.childNodes.length; i++) {
            var child = tc.childNodes[i];
            if (child.nodeType != 1) {
                continue;
            };
            var tab = new Tab();
            var id = tab.initialize(doc, this, child);
            tabs[id] = tab;
        };
        // and unrender them
        while (tc.hasChildNodes()) {
            tc.removeChild(tc.childNodes[0]);
        };
    };

    TabManager.prototype.render = function() {
        /* sets up the tabs */
        var doc = this.doc;
        var container = this.tabcontainer = doc.createElement('div');
        container.className = 'tabmanager-container';
        var numcols = this.active_tabs.length;
        
        var tabrow = this.tabrow = this.doc.createElement('div');
        tabrow.className = 'tabmanager-tabrow';
        for (var i=0; i < this.active_tabs.length; i++) {
            var tab = this.active_tabs[i];
            var tabel = doc.createElement('div');
            tabel.className = 'tabmanager-tab';
            var tabtext = doc.createTextNode(tab.name);
            tabel.appendChild(tabtext);
            misclib.addEventHandler(tabel, 'click', this.clickTab, this, i);
            // place a reference to the tab th on the tab
            tab.button = tabel;
            if (tab.helpcontent) {
                var helplink = doc.createElement('span');
                helplink.appendChild(doc.createTextNode('\xa0?\xa0'));
                helplink.className = 'helplink';
                tabel.appendChild(helplink);
                misclib.addEventHandler(helplink, 'click', tab.displayHelp,
                                        tab);
            };
            tabrow.appendChild(tabel);
        };
        var filler = this.doc.createElement('div');
        filler.className = 'tabmanager-filler';
        filler.appendChild(this.doc.createTextNode('\xa0'));
        tabrow.appendChild(filler);
        container.appendChild(tabrow);

        var contentel = this.contentel = doc.createElement('div');
        contentel.className = 'tabmanager-content';
        container.appendChild(contentel);
        // render the contents of the first tab into the body
        this.activateTab(0);

        this.tabscontainer.appendChild(container);
    };

    TabManager.prototype.unrender = function() {
        /* remove all tabs */
        for (var i=0; i < this.active_tabs.length; i++) {
            this.active_tabs[i].beforeHide();
        };
        while (this.tabscontainer.hasChildNodes()) {
            this.tabscontainer.removeChild(this.tabscontainer.childNodes[0]);
        };
        this.current_active_tab = null;
        for (var i=0; i < this.active_tabs.length; i++) {
            var tab = this.active_tabs[i];
            var rendered = tab.rendered;
            tab.rendered = null;
            delete rendered;
            tab.afterHide();
        };
        this.active_tabs = [];
    };

    TabManager.prototype.clickTab = function(event, tabindex) {
        /* handle a tab click */
        if (this.current_active_tab_index != null) {
            var tab = this.active_tabs[this.current_active_tab_index];
            tab.beforeHide();
            this.contentel.removeChild(this.contentel.firstChild);
            tab.afterHide();
        };
        this.activateTab(tabindex);
    };

    TabManager.prototype.activateTab = function(index, reload) {
        this.current_active_tab_index = index;
        var tab = this.active_tabs[index];
        // use rendered version if already available
        var bodycontents = (tab.rendered && tab.use_rendered) ? 
                                tab.rendered : tab.getContents(reload);
        tab.beforeShow();
        this.contentel.appendChild(bodycontents);
        for (var i=0; i < this.tabrow.childNodes.length; i++) {
            var child = this.tabrow.childNodes[i];
            if (child.className == 'tabmanager-filler') {
                continue;
            };
            var classname = 'tabmanager-tab';
            var islast = (i == this.tabrow.childNodes.length - 2);
            if (i == index && islast) {
                classname = 'tabmanager-tab tabmanager-last ' +
                            'tabmanager-selected';
            } else if (islast) {
                classname = 'tabmanager-tab tabmanager-last';
            } else if (i == index) {
                classname = 'tabmanager-tab tabmanager-selected';
            };
            child.className = classname;
        };
        tab.afterShow();
    };

    TabManager.prototype.addTab = function(tabid, prepareFunction) {
        /* add a tab to the current layout */
        var tab = this.tabs[tabid];
        tab.prepareFunction = prepareFunction;
        this.active_tabs.push(this.tabs[tabid]);
    };

    TabManager.prototype.usedTabs = function() {
        /* return a list of ids of all tabs that are currently in use */
        var ids = [];
        for (var i=0; i < this.active_tabs.length; i++) {
            ids.push(this.active_tabs[i].id);
        };
        return ids;
    };

    TabManager.prototype.reload = function() {
        while (this.tabscontainer.hasChildNodes()) {
            this.tabscontainer.removeChild(this.tabscontainer.childNodes[0]);
        };
        for (var i=0; i < this.active_tabs.length; i++) {
            this.active_tabs[i].reload();
        };
        this.render();
    };

    //------------------------------------------------------------------------
    // Tab class
    //------------------------------------------------------------------------

    function Tab() {
        /* manages the contents of a single tab */
    };

    this.Tab = Tab;

    Tab.prototype.initialize = function(doc, tabmanager, templateel) {
        this.doc = doc;
        this.tabmanager = tabmanager;
        // see if it contains a help section
        for (var i=0; i < templateel.childNodes.length; i++) {
            var child = templateel.childNodes[i];
            if (child.nodeType == 1 && child.getAttribute('type') == 'help') {
                this.helpcontent = child;
                templateel.removeChild(child);
            };
        };
        // create a copy of the template and save that
        this.template = templateel.cloneNode(true);
        // cache
        this.rendered = null;
        // whether the cached version should be used
        this.use_rendered = true;
        this._parseTemplateEl(templateel);
        return this.id;
    };

    Tab.prototype._parseTemplateEl = function(el) {
        /* get data from the template el and store it */
        this.id = el.getAttribute('id');
        this.name = el.getAttribute('tabname');
    };

    Tab.prototype.beforeShow = function() {
        /* will be called just before this tab is shown */
    };

    Tab.prototype.afterShow = function() {
        /* will be called just after the tab is shown */
    };

    Tab.prototype.beforeHide = function() {
        /* will be called just before the tab is hidden */
    };

    Tab.prototype.afterHide = function() {
        /* will be called just after the tab is hidden */
    };

    Tab.prototype.getContents = function(reload) {
        var content = this.template;
        if (this.prepareFunction) {
            content = content.cloneNode(true);
            this.rendered = content;
            this.prepareFunction(this, content, reload);
            content = this.rendered; // allow prepareFunction to mess with it
        };
        return this.rendered;
    };

    Tab.prototype.reload = function() {
        this.rendered = null;
        while (this.tabmanager.contentel.hasChildNodes()) {
            this.tabmanager.contentel.removeChild(
                    this.tabmanager.contentel.firstChild);
        };
        this.tabmanager.activateTab(this.tabmanager.current_active_tab_index,
                                    true);
    };

    Tab.prototype.displayHelp = function(event) {
        var win = window.open('about:blank', 'win', 
                'width=500,height=350,scrollbars=yes,status=no,toolbar=no');
        var doc = this.doc;
        // create the full html content for the window, then serialize and
        // write to the doc (Mozilla seems to not like appendChild for this
        // purpose)
        var html = doc.createElement('html');
        var head = doc.createElement('head');
        html.appendChild(head);
        var title = doc.createElement('title');
        head.appendChild(title);
        title.appendChild(doc.createTextNode('Help text for ' + this.name));
        // create link elements
        var stylesheets = [];
        if (this.helpcontent.getAttribute('stylesheets')) {
            stylesheets = 
                this.helpcontent.getAttribute('stylesheets').split(' ');
        } else {
            var parentlinks = this.doc.getElementsByTagName('link');
            for (var i=0; i < parentlinks.length; i++) {
                var parentlink = parentlinks[i];
                if (parentlink.getAttribute('rel') == 'stylesheet' && 
                        parentlink.getAttribute('type') == 'text/css') {
                    stylesheets.push(parentlink.href);
                };
            };
        };
        for (var i=0; i < stylesheets.length; i++) {
            var link = doc.createElement('link');
            link.setAttribute('type', 'text/css');
            link.setAttribute('rel', 'stylesheet');
            link.setAttribute('href', stylesheets[i]);
            head.appendChild(link);
        };
        var body = doc.createElement('body');
        html.appendChild(body);
        body.appendChild(this.helpcontent);
        var html = '<html>' + html.innerHTML + '</html>';
        win.document.write(html);
        win.document.close();
        event.cancelBubble = true;
        if (event.preventDefaut) {
            event.preventDefault();
        } else {
            event.returnValue = false;
        };
        win.focus();
        delete html;
    };
}();


