| /* |
| 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 <widgetId>-addChild, <widgetId>-removeChild, and <widgetId>-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: "<div dojoAttachPoint='containerNode'></div>", |
| |
| // 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<this.children.length; i++){ |
| if(this.children[i].selected){ |
| this.selectChild(this.children[i]); |
| break; |
| } |
| } |
| if(!this.selectedChildWidget){ |
| this.selectChild(this.children[0]); |
| } |
| } |
| } |
| }, |
| |
| addChild: function(child){ |
| dojo.widget.PageContainer.superclass.addChild.apply(this, arguments); |
| this._setupChild(child); |
| |
| // in case the tab labels have overflowed from one line to two lines |
| this.onResized(); |
| |
| // if this is the first child, then select it |
| if(!this.selectedChildWidget){ |
| this.selectChild(child); |
| } |
| }, |
| |
| _setupChild: function(/*Widget*/ page){ |
| // Summary: Add the given child to this page container |
| |
| page.hide(); |
| |
| // 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"; |
| |
| // publish the addChild event for panes added via addChild(), and the original panes too |
| dojo.event.topic.publish(this.widgetId+"-addChild", page); |
| }, |
| |
| removeChild: function(/*Widget*/ page){ |
| dojo.widget.PageContainer.superclass.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; } |
| |
| // this will notify any tablists to remove a button; do this first because it may affect sizing |
| dojo.event.topic.publish(this.widgetId+"-removeChild", page); |
| |
| // in case the tab labels now take up one line instead of two lines |
| this.onResized(); |
| |
| if (this.selectedChildWidget === page) { |
| this.selectedChildWidget = undefined; |
| if (this.children.length > 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: "<span wairole='tablist' dojoAttachEvent='onKey'></span>", |
| 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: "<span class='item'>" + |
| "<span dojoAttachEvent='onClick' dojoAttachPoint='titleNode' class='selectButton'>${this.label}</span>" + |
| "<span dojoAttachEvent='onClick:onCloseButtonClick' class='closeButton'>[X]</span>" + |
| "</span>", |
| |
| // 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; |
| } |
| }); |