blob: 737851cfdbf2cc7796d8fec15d7ad492b7e84a28 [file] [log] [blame]
/*
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;
}
});