Subversion Repositories Applications.papyrus

Rev

Blame | Last modification | View Log | RSS feed

if(!dojo._hasResource["dijit.layout.StackContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit.layout.StackContainer"] = true;
dojo.provide("dijit.layout.StackContainer");

dojo.require("dijit._Templated");
dojo.require("dijit.layout._LayoutWidget");
dojo.require("dijit.form.Button");

dojo.declare(
        "dijit.layout.StackContainer",
        dijit.layout._LayoutWidget,

        // summary
        //      A container that has multiple children, but shows only
        //      one child at a time (like looking at the pages in a book one by one).
        //
        //      Publishes topics <widgetId>-addChild, <widgetId>-removeChild, and <widgetId>-selectChild
        //
        //      Can be base class for container, Wizard, Show, etc.
{
        // doLayout: Boolean
        //  if true, change the size of my currently displayed child to match my size
        doLayout: true,

        _started: false,

        // selectedChildWidget: Widget
        //      References the currently selected child widget, if any

        postCreate: function(){
                dijit.setWaiRole((this.containerNode || this.domNode), "tabpanel");
                this.connect(this.domNode, "onkeypress", this._onKeyPress);
        },
        
        startup: function(){
                if(this._started){ return; }

                var children = this.getChildren();

                // Setup each page panel
                dojo.forEach(children, this._setupChild, this);

                // Figure out which child to initially display
                dojo.some(children, function(child){
                        if(child.selected){
                                this.selectedChildWidget = child;
                        }
                        return child.selected;
                }, this);

                var selected = this.selectedChildWidget;

                // Default to the first child
                if(!selected && children[0]){
                        selected = this.selectedChildWidget = children[0];
                        selected.selected = true;
                }
                if(selected){
                        this._showChild(selected);
                }

                // Now publish information about myself so any StackControllers can initialize..
                dojo.publish(this.id+"-startup", [{children: children, selected: selected}]);
                this.inherited("startup",arguments);
                this._started = true;
        },

        _setupChild: function(/*Widget*/ page){
                // Summary: prepare the given child

                page.domNode.style.display = "none";

                // since we are setting the width/height of the child elements, they need
                // to be position:relative, or IE has problems (See bug #2033)
                page.domNode.style.position = "relative";

                return page; // dijit._Widget
        },

        addChild: function(/*Widget*/ child, /*Integer?*/ insertIndex){
                // summary: Adds a widget to the stack
                 
                dijit._Container.prototype.addChild.apply(this, arguments);
                child = this._setupChild(child);

                if(this._started){
                        // in case the tab titles have overflowed from one line to two lines
                        this.layout();

                        dojo.publish(this.id+"-addChild", [child, insertIndex]);

                        // if this is the first child, then select it
                        if(!this.selectedChildWidget){
                                this.selectChild(child);
                        }
                }
        },

        removeChild: function(/*Widget*/ page){
                // summary: Removes the pane from the stack

                dijit._Container.prototype.removeChild.apply(this, arguments);

                // If we are being destroyed than don't run the code below (to select another page), because we are deleting
                // every page one by one
                if(this._beingDestroyed){ return; }

                if(this._started){
                        // this will notify any tablists to remove a button; do this first because it may affect sizing
                        dojo.publish(this.id+"-removeChild", [page]);

                        // in case the tab titles now take up one line instead of two lines
                        this.layout();
                }

                if(this.selectedChildWidget === page){
                        this.selectedChildWidget = undefined;
                        if(this._started){
                                var children = this.getChildren();
                                if(children.length){
                                        this.selectChild(children[0]);
                                }
                        }
                }
        },

        selectChild: function(/*Widget*/ page){
                // summary:
                //      Show the given widget (which must be one of my children)

                page = dijit.byId(page);

                if(this.selectedChildWidget != page){
                        // Deselect old page and select new one
                        this._transition(page, this.selectedChildWidget);
                        this.selectedChildWidget = page;
                        dojo.publish(this.id+"-selectChild", [page]);
                }
        },

        _transition: function(/*Widget*/newWidget, /*Widget*/oldWidget){
                if(oldWidget){
                        this._hideChild(oldWidget);
                }
                this._showChild(newWidget);

                // Size the new widget, in case this is the first time it's being shown,
                // or I have been resized since the last time it was shown.
                // page must be visible for resizing to work
                if(this.doLayout && newWidget.resize){
                        newWidget.resize(this._containerContentBox || this._contentBox);
                }
        },

        _adjacent: function(/*Boolean*/ forward){
                // summary: Gets the next/previous child widget in this container from the current selection
                var children = this.getChildren();
                var index = dojo.indexOf(children, this.selectedChildWidget);
                index += forward ? 1 : children.length - 1;
                return children[ index % children.length ]; // dijit._Widget
        },

        forward: function(){
                // Summary: advance to next page
                this.selectChild(this._adjacent(true));
        },

        back: function(){
                // Summary: go back to previous page
                this.selectChild(this._adjacent(false));
        },

        _onKeyPress: function(e){
                dojo.publish(this.id+"-containerKeyPress", [{ e: e, page: this}]);
        },

        layout: function(){
                if(this.doLayout && this.selectedChildWidget && this.selectedChildWidget.resize){
                        this.selectedChildWidget.resize(this._contentBox);
                }
        },

        _showChild: function(/*Widget*/ page){
                var children = this.getChildren();
                page.isFirstChild = (page == children[0]);
                page.isLastChild = (page == children[children.length-1]);
                page.selected = true;

                page.domNode.style.display="";
                if(page._loadCheck){
                        page._loadCheck(); // trigger load in ContentPane
                }
                if(page.onShow){
                        page.onShow();
                }
        },

        _hideChild: function(/*Widget*/ page){
                page.selected=false;
                page.domNode.style.display="none";
                if(page.onHide){
                        page.onHide();
                }
        },

        closeChild: function(/*Widget*/ page){
                // summary
                //      callback when user clicks the [X] to remove a page
                //      if onClose() returns true then remove and destroy the childd
                var remove = page.onClose(this, page);
                if(remove){
                        this.removeChild(page);
                        // makes sure we can clean up executeScripts in ContentPane onUnLoad
                        page.destroy();
                }
        },

        destroy: function(){
                this._beingDestroyed = true;
                this.inherited("destroy",arguments);
        }
});

dojo.declare(
        "dijit.layout.StackController",
        [dijit._Widget, dijit._Templated, dijit._Container],
        {
                // summary:
                //      Set of buttons to select a page in a page list.
                //      Monitors the specified StackContainer, and whenever a page is
                //      added, deleted, or selected, updates itself accordingly.

                templateString: "<span wairole='tablist' dojoAttachEvent='onkeypress' class='dijitStackController'></span>",

                // containerId: String
                //      the id of the page container that I point to
                containerId: "",

                // buttonWidget: String
                //      the name of the button widget to create to correspond to each page
                buttonWidget: "dijit.layout._StackButton",

                postCreate: function(){
                        dijit.setWaiRole(this.domNode, "tablist");

                        this.pane2button = {};          // mapping from panes to buttons
                        this._subscriptions=[
                                dojo.subscribe(this.containerId+"-startup", this, "onStartup"),
                                dojo.subscribe(this.containerId+"-addChild", this, "onAddChild"),
                                dojo.subscribe(this.containerId+"-removeChild", this, "onRemoveChild"),
                                dojo.subscribe(this.containerId+"-selectChild", this, "onSelectChild"),
                                dojo.subscribe(this.containerId+"-containerKeyPress", this, "onContainerKeyPress")
                        ];
                },

                onStartup: function(/*Object*/ info){
                        // summary: called after StackContainer has finished initializing
                        dojo.forEach(info.children, this.onAddChild, this);
                        this.onSelectChild(info.selected);
                },

                destroy: function(){
                        dojo.forEach(this._subscriptions, dojo.unsubscribe);
                        this.inherited("destroy",arguments);
                },

                onAddChild: function(/*Widget*/ page, /*Integer?*/ insertIndex){
                        // summary:
                        //   Called whenever a page is added to the container.
                        //   Create button corresponding to the page.

                        // add a node that will be promoted to the button widget
                        var refNode = document.createElement("span");
                        this.domNode.appendChild(refNode);
                        // create an instance of the button widget
                        var cls = dojo.getObject(this.buttonWidget);
                        var button = new cls({label: page.title, closeButton: page.closable}, refNode);
                        this.addChild(button, insertIndex);
                        this.pane2button[page] = button;
                        page.controlButton = button;    // this value might be overwritten if two tabs point to same container
                        
                        dojo.connect(button, "onClick", dojo.hitch(this,"onButtonClick",page));
                        dojo.connect(button, "onClickCloseButton", dojo.hitch(this,"onCloseButtonClick",page));
                        
                        if(!this._currentChild){ // put the first child into the tab order
                                button.focusNode.setAttribute("tabIndex","0");
                                this._currentChild = page;
                        }
                },

                onRemoveChild: function(/*Widget*/ page){
                        // summary:
                        //   Called whenever a page is removed from the container.
                        //   Remove the button corresponding to the page.
                        if(this._currentChild === page){ this._currentChild = null; }
                        var button = this.pane2button[page];
                        if(button){
                                // TODO? if current child { reassign }
                                button.destroy();
                        }
                        this.pane2button[page] = null;
                },

                onSelectChild: function(/*Widget*/ page){
                        // summary:
                        //      Called when a page has been selected in the StackContainer, either by me or by another StackController

                        if(!page){ return; }

                        if(this._currentChild){
                                var oldButton=this.pane2button[this._currentChild];
                                oldButton.setChecked(false);
                                oldButton.focusNode.setAttribute("tabIndex", "-1");
                        }

                        var newButton=this.pane2button[page];
                        newButton.setChecked(true);
                        this._currentChild = page;
                        newButton.focusNode.setAttribute("tabIndex", "0");
                },

                onButtonClick: function(/*Widget*/ page){
                        // summary:
                        //   Called whenever one of my child buttons is pressed in an attempt to select a page
                        var container = dijit.byId(this.containerId);   // TODO: do this via topics?
                        container.selectChild(page); 
                },

                onCloseButtonClick: function(/*Widget*/ page){
                        // summary:
                        //   Called whenever one of my child buttons [X] is pressed in an attempt to close a page
                        var container = dijit.byId(this.containerId);
                        container.closeChild(page);
                        var b = this.pane2button[this._currentChild];
                        if(b){
                                dijit.focus(b.focusNode || b.domNode);
                        }
                },
                
                // TODO: this is a bit redundant with forward, back api in StackContainer
                adjacent: function(/*Boolean*/ forward){
                        // find currently focused button in children array
                        var children = this.getChildren();
                        var current = dojo.indexOf(children, this.pane2button[this._currentChild]);
                        // pick next button to focus on
                        var offset = forward ? 1 : children.length - 1;
                        return children[ (current + offset) % children.length ]; // dijit._Widget
                },

                onkeypress: function(/*Event*/ e){
                        // summary:
                        //   Handle keystrokes on the page list, for advancing to next/previous button
                        //   and closing the current page if the page is closable.

                        if(this.disabled || e.altKey ){ return; }
                        var forward = true;
                        if(e.ctrlKey || !e._djpage){
                                var k = dojo.keys;
                                switch(e.keyCode){
                                        case k.LEFT_ARROW:
                                        case k.UP_ARROW:
                                        case k.PAGE_UP:
                                                forward = false;
                                                // fall through
                                        case k.RIGHT_ARROW:
                                        case k.DOWN_ARROW:
                                        case k.PAGE_DOWN:
                                                this.adjacent(forward).onClick();
                                                dojo.stopEvent(e);
                                                break;
                                        case k.DELETE:
                                                if(this._currentChild.closable){
                                                        this.onCloseButtonClick(this._currentChild);
                                                }
                                                dojo.stopEvent(e);
                                                break;
                                        default:
                                                if(e.ctrlKey){
                                                        if(e.keyCode == k.TAB){
                                                                this.adjacent(!e.shiftKey).onClick();
                                                                dojo.stopEvent(e);
                                                        }else if(e.keyChar == "w"){
                                                                if(this._currentChild.closable){
                                                                        this.onCloseButtonClick(this._currentChild);
                                                                }
                                                                dojo.stopEvent(e); // avoid browser tab closing.
                                                        }
                                                }
                                }
                        }
                },

                onContainerKeyPress: function(/*Object*/ info){
                        info.e._djpage = info.page;
                        this.onkeypress(info.e);
                }
});

dojo.declare("dijit.layout._StackButton",
        dijit.form.ToggleButton,
        {
        // summary
        //      Internal widget used by StackContainer.
        //      The button-like or tab-like object you click to select or delete a page
        
        tabIndex: "-1", // StackContainer buttons are not in the tab order by default
        
        postCreate: function(/*Event*/ evt){
                dijit.setWaiRole((this.focusNode || this.domNode), "tab");
                this.inherited("postCreate", arguments);
        },
        
        onClick: function(/*Event*/ evt){
                // summary: This is for TabContainer where the tabs are <span> rather than button,
                //      so need to set focus explicitly (on some browsers)
                dijit.focus(this.focusNode);

                // ... now let StackController catch the event and tell me what to do
        },

        onClickCloseButton: function(/*Event*/ evt){
                // summary
                //      StackContainer connects to this function; if your widget contains a close button
                //      then clicking it should call this function.
                evt.stopPropagation();
        }
});

// These arguments can be specified for the children of a StackContainer.
// Since any widget can be specified as a StackContainer child, mix them
// into the base widget class.  (This is a hack, but it's effective.)
dojo.extend(dijit._Widget, {
        // title: String
        //              Title of this widget.  Used by TabContainer to the name the tab, etc.
        title: "",

        // selected: Boolean
        //              Is this child currently selected?
        selected: false,

        // closable: Boolean
        //              True if user can close (destroy) this child, such as (for example) clicking the X on the tab.
        closable: false,        // true if user can close this tab pane

        onClose: function(){
                // summary: Callback if someone tries to close the child, child will be closed if func returns true
                return true;
        }
});

}