/* Copyright (c) 2004-2006, The Dojo Foundation All Rights Reserved. Licensed under the Academic Free License version 2.1 or above OR the modified BSD license. For more information on Dojo licensing, see: http://dojotoolkit.org/community/licensing.shtml */ dojo.provide("dojo.widget.PageContainer"); dojo.require("dojo.lang.func"); dojo.require("dojo.widget.*"); dojo.require("dojo.event.*"); dojo.require("dojo.html.selection"); dojo.widget.defineWidget("dojo.widget.PageContainer", dojo.widget.HtmlWidget, { // 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 -addChild, -removeChild, and -selectChild // // Can be base class for container, Wizard, Show, etc. isContainer: true, // doLayout: Boolean // if true, change the size of my currently displayed child to match my size doLayout: true, templateString: "
", // selectedChild: String // id of the currently shown page selectedChild: "", fillInTemplate: function(args, frag) { // Copy style info from input node to output node var source = this.getFragNodeRef(frag); dojo.html.copyStyle(this.domNode, source); dojo.widget.PageContainer.superclass.fillInTemplate.apply(this, arguments); }, postCreate: function(args, frag) { if(this.children.length){ // Setup each page panel dojo.lang.forEach(this.children, this._setupChild, this); // Figure out which child to initially display var initialChild; if(this.selectedChild){ this.selectChild(this.selectedChild); }else{ for(var i=0; i 0) { this.selectChild(this.children[0], true); } } }, selectChild: function(/*Widget*/ page, /*Widget*/ callingWidget){ // summary // Show the given widget (which must be one of my children) page = dojo.widget.byId(page); this.correspondingPageButton = callingWidget; // Deselect old page and select new one if(this.selectedChildWidget){ this._hideChild(this.selectedChildWidget); } this.selectedChildWidget = page; this.selectedChild = page.widgetId; this._showChild(page); page.isFirstChild = (page == this.children[0]); page.isLastChild = (page == this.children[this.children.length-1]); dojo.event.topic.publish(this.widgetId+"-selectChild", page); }, forward: function(){ // Summary: advance to next page var index = dojo.lang.find(this.children, this.selectedChildWidget); this.selectChild(this.children[index+1]); }, back: function(){ // Summary: go back to previous page var index = dojo.lang.find(this.children, this.selectedChildWidget); this.selectChild(this.children[index-1]); }, onResized: function(){ // Summary: called when any page is shown, to make it fit the container correctly if(this.doLayout && this.selectedChildWidget){ with(this.selectedChildWidget.domNode.style){ top = dojo.html.getPixelValue(this.containerNode, "padding-top", true); left = dojo.html.getPixelValue(this.containerNode, "padding-left", true); } var content = dojo.html.getContentBox(this.containerNode); this.selectedChildWidget.resizeTo(content.width, content.height); } }, _showChild: function(/*Widget*/ page) { // size the current page (in case this is the first time it's being shown, or I have been resized) if(this.doLayout){ var content = dojo.html.getContentBox(this.containerNode); page.resizeTo(content.width, content.height); } page.selected=true; page.show(); }, _hideChild: function(/*Widget*/ page) { page.selected=false; page.hide(); }, 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; dojo.event.topic.destroy(this.widgetId+"-addChild"); dojo.event.topic.destroy(this.widgetId+"-removeChild"); dojo.event.topic.destroy(this.widgetId+"-selectChild"); dojo.widget.PageContainer.superclass.destroy.apply(this, arguments); } }); dojo.widget.defineWidget( "dojo.widget.PageController", dojo.widget.HtmlWidget, { // summary // Set of buttons to select a page in a page list. // Monitors the specified PageContaine, and whenever a page is // added, deleted, or selected, updates itself accordingly. templateString: "", isContainer: true, // 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: "PageButton", // class: String // Class name to apply to the top dom node "class": "dojoPageController", fillInTemplate: function() { dojo.html.addClass(this.domNode, this["class"]); // "class" is a reserved word in JS dojo.widget.wai.setAttr(this.domNode, "waiRole", "role", "tablist"); }, postCreate: function(){ this.pane2button = {}; // mapping from panes to buttons // If children have already been added to the page container then create buttons for them var container = dojo.widget.byId(this.containerId); if(container){ dojo.lang.forEach(container.children, this.onAddChild, this); } dojo.event.topic.subscribe(this.containerId+"-addChild", this, "onAddChild"); dojo.event.topic.subscribe(this.containerId+"-removeChild", this, "onRemoveChild"); dojo.event.topic.subscribe(this.containerId+"-selectChild", this, "onSelectChild"); }, destroy: function(){ dojo.event.topic.unsubscribe(this.containerId+"-addChild", this, "onAddChild"); dojo.event.topic.unsubscribe(this.containerId+"-removeChild", this, "onRemoveChild"); dojo.event.topic.unsubscribe(this.containerId+"-selectChild", this, "onSelectChild"); dojo.widget.PageController.superclass.destroy.apply(this, arguments); }, onAddChild: function(/*Widget*/ page){ // summary // Called whenever a page is added to the container. // Create button corresponding to the page. var button = dojo.widget.createWidget(this.buttonWidget, { label: page.label, closeButton: page.closable }); this.addChild(button); this.domNode.appendChild(button.domNode); this.pane2button[page]=button; page.controlButton = button; // this value might be overwritten if two tabs point to same container var _this = this; dojo.event.connect(button, "onClick", function(){ _this.onButtonClick(page); }); dojo.event.connect(button, "onCloseButtonClick", function(){ _this.onCloseButtonClick(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){ button.destroy(); } this.pane2button[page] = null; }, onSelectChild: function(/*Widget*/ page){ // Summary // Called when a page has been selected in the PageContainer, either by me or by another PageController if(this._currentChild){ var oldButton=this.pane2button[this._currentChild]; oldButton.clearSelected(); } var newButton=this.pane2button[page]; newButton.setSelected(); this._currentChild=page; }, onButtonClick: function(/*Widget*/ page){ // summary // Called whenever one of my child buttons is pressed in an attempt to select a page var container = dojo.widget.byId(this.containerId); // TODO: do this via topics? container.selectChild(page, false, this); }, onCloseButtonClick: function(/*Widget*/ page){ // summary // Called whenever one of my child buttons [X] is pressed in an attempt to close a page var container = dojo.widget.byId(this.containerId); container.closeChild(page); }, onKey: function(/*Event*/ evt){ // summary: // Handle keystrokes on the page list, for advancing to next/previous button if( (evt.keyCode == evt.KEY_RIGHT_ARROW)|| (evt.keyCode == evt.KEY_LEFT_ARROW) ){ var current = 0; var next = null; // the next button to focus on // find currently focused button in children array var current = dojo.lang.find(this.children, this.pane2button[this._currentChild]); // pick next button to focus on if(evt.keyCode == evt.KEY_RIGHT_ARROW){ next = this.children[ (current+1) % this.children.length ]; }else{ // is LEFT_ARROW next = this.children[ (current+ (this.children.length-1)) % this.children.length ]; } dojo.event.browser.stopEvent(evt); next.onClick(); } } } ); dojo.widget.defineWidget("dojo.widget.PageButton", dojo.widget.HtmlWidget, { // summary // Internal widget used by PageList. // The button-like or tab-like object you click to select or delete a page templateString: "" + "${this.label}" + "[X]" + "", // label: String // Name to print on the button label: "foo", // closeButton: Boolean // true iff we should also print a close icon to destroy corresponding page closeButton: false, onClick: function(){ // summary // Basically this is the attach point PageController listens to, to select the page this.focus(); }, onCloseButtonMouseOver: function(){ // summary // The close button changes color a bit when you mouse over dojo.html.addClass(this.closeButtonNode, "closeHover"); }, onCloseButtonMouseOut: function(){ // summary // Revert close button to normal color on mouse out dojo.html.removeClass(this.closeButtonNode, "closeHover"); }, onCloseButtonClick: function(/*Event*/ evt){ // summary // Handle clicking the close button for this tab }, setSelected: function(){ // summary // This is run whenever the page corresponding to this button has been selected dojo.html.addClass(this.domNode, "current"); this.titleNode.setAttribute("tabIndex","0"); }, clearSelected: function(){ // summary // This function is run whenever the page corresponding to this button has been deselected (and another page has been shown) dojo.html.removeClass(this.domNode, "current"); this.titleNode.setAttribute("tabIndex","-1"); }, focus: function(){ // summary // This will focus on the this button (for accessibility you need to do this when the button is selected) if(this.titleNode.focus){ // mozilla 1.7 doesn't have focus() func this.titleNode.focus(); } } }); // These arguments can be specified for the children of a PageContainer. // Since any widget can be specified as a PageContainer child, mix them // into the base widget class. (This is a hack, but it's effective.) dojo.lang.extend(dojo.widget.Widget, { // label: String // Label or title of this widget. Used by TabContainer to the name the tab, etc. label: "", // 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; } });