@qooxdoo/framework
Version:
The JS Framework for Coders
657 lines (555 loc) • 17.9 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2004-2008 1&1 Internet AG, Germany, http://www.1und1.de
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Sebastian Werner (wpbasti)
* Andreas Ecker (ecker)
* Martin Wittemann (martinwittemann)
* Jonathan Weiß (jonathan_rass)
* Christian Hagendorn (chris_schmidt)
************************************************************************ */
/**
* A tab view is a multi page view where only one page is visible
* at each moment. It is possible to switch the pages using the
* buttons rendered by each page.
*
* Note that prior to v6.0, when changing the currently selected tab via code
* (ie changing the selection property) TabView would automatically set the
* focus to that tab; this is undesirable (and inconsistent with other parts
* of the framework) and is no longer done automatically.
*
* @childControl bar {qx.ui.container.SlideBar} slidebar for all tab buttons
* @childControl pane {qx.ui.container.Stack} stack container to show one tab page
*/
qx.Class.define("qx.ui.tabview.TabView", {
extend: qx.ui.core.Widget,
implement: qx.ui.core.ISingleSelection,
include: [qx.ui.core.MContentPadding],
/*
*****************************************************************************
CONSTRUCTOR
*****************************************************************************
*/
/**
* @param barPosition {String} Initial bar position ({@link #barPosition})
*/
construct(barPosition) {
super();
this.__barPositionToState = {
top: "barTop",
right: "barRight",
bottom: "barBottom",
left: "barLeft"
};
this._createChildControl("bar");
this._createChildControl("pane");
// Create manager
var mgr = (this.__radioGroup = this._createRadioGroupInstance());
mgr.setWrap(false);
mgr.addListener("changeSelection", this._onChangeSelection, this);
// Initialize bar position
if (barPosition != null) {
this.setBarPosition(barPosition);
} else {
this.initBarPosition();
}
// ARIA attrs
this.getContentElement().setAttribute("role", "tablist");
},
/*
*****************************************************************************
EVENTS
*****************************************************************************
*/
events: {
/** Fires after the selection was modified */
changeSelection: "qx.event.type.Data",
/** Fires after the value was modified */
changeValue: "qx.event.type.Data"
},
/*
*****************************************************************************
PROPERTIES
*****************************************************************************
*/
properties: {
// overridden
appearance: {
refine: true,
init: "tabview"
},
/**
* This property defines on which side of the TabView the bar should be positioned.
*/
barPosition: {
check: ["left", "right", "top", "bottom"],
init: "top",
apply: "_applyBarPosition"
}
},
/*
*****************************************************************************
MEMBERS
*****************************************************************************
*/
members: {
/** @type {qx.ui.form.RadioGroup} instance containing the radio group */
__radioGroup: null,
/**
* setValue implements part of the {@link qx.ui.form.IField} interface.
*
* @param item {null|qx.ui.tabview.Page} Page to set as selected value.
* @returns {null|TypeError} The status of this operation.
*/
setValue(item) {
if (null === item) {
this.resetSelection();
return null;
}
if (item instanceof qx.ui.tabview.Page) {
this.setSelection([item]);
return null;
} else {
return new TypeError(
"Given argument is not null or a {qx.ui.tabview.Page}."
);
}
},
/**
* getValue implements part of the {@link qx.ui.form.IField} interface.
*
* @returns {null|qx.ui.tabview.Page} The currently selected page or null if there is none.
*/
getValue() {
var pages = this.getSelection();
if (pages.length) {
return pages[0];
} else {
return null;
}
},
/**
* resetValue implements part of the {@link qx.ui.form.IField} interface.
*/
resetValue() {
this.resetSelection();
},
/*
---------------------------------------------------------------------------
WIDGET API
---------------------------------------------------------------------------
*/
// overridden
_createChildControlImpl(id, hash) {
var control;
switch (id) {
case "bar":
control = new qx.ui.container.SlideBar();
control.setZIndex(10);
this._add(control);
break;
case "pane":
control = new qx.ui.container.Stack();
control.setZIndex(5);
this._add(control, { flex: 1 });
break;
}
return control || super._createChildControlImpl(id);
},
/**
* Creates the radio group manager instance.
*
* Allows override customizations of the instance
*
* @return {qx.ui.form.RadioGroup}
*/
_createRadioGroupInstance() {
return new qx.ui.form.RadioGroup();
},
/**
* Returns the element, to which the content padding should be applied.
*
* @return {qx.ui.core.Widget} The content padding target.
*/
_getContentPaddingTarget() {
return this.getChildControl("pane");
},
/*
---------------------------------------------------------------------------
CHILDREN HANDLING
---------------------------------------------------------------------------
*/
/**
* Adds a page to the tabview including its needed button
* (contained in the page).
*
* @param page {qx.ui.tabview.Page} The page which should be added.
*/
add(page) {
if (qx.core.Environment.get("qx.debug")) {
if (!(page instanceof qx.ui.tabview.Page)) {
throw new Error("Incompatible child for TabView: " + page);
}
}
var button = page.getButton();
var bar = this.getChildControl("bar");
var pane = this.getChildControl("pane");
// Exclude page
page.exclude();
// Add button and page
bar.add(button);
pane.add(page);
// Register button
this.__radioGroup.add(button);
// Add state to page
page.addState(this.__barPositionToState[this.getBarPosition()]);
this.__updateFirstLastTabStates();
page.addListener("close", this._onPageClose, this);
page.addListener(
"changeTabVisibility",
this.__onPageChangeTabVisibility,
this
);
},
/**
* Adds a page to the tabview including its needed button
* (contained in the page).
*
* @param page {qx.ui.tabview.Page} The page which should be added.
* @param index {Integer?null} Optional position where to add the page.
*/
addAt(page, index) {
if (qx.core.Environment.get("qx.debug")) {
if (!(page instanceof qx.ui.tabview.Page)) {
throw new Error("Incompatible child for TabView: " + page);
}
}
var children = this.getChildren();
if (!(index == null) && index > children.length) {
throw new Error("Index should be less than : " + children.length);
}
if (index == null) {
index = children.length;
}
var button = page.getButton();
var bar = this.getChildControl("bar");
var pane = this.getChildControl("pane");
// Exclude page
page.exclude();
// Add button and page
bar.addAt(button, index);
pane.addAt(page, index);
// Register button
this.__radioGroup.add(button);
// Add state to page
page.addState(this.__barPositionToState[this.getBarPosition()]);
// Update states
this.__updateFirstLastTabStates();
page.addListener("close", this._onPageClose, this);
page.addListener(
"changeTabVisibility",
this.__onPageChangeTabVisibility,
this
);
},
/**
* Removes a page (and its corresponding button) from the TabView.
*
* @param page {qx.ui.tabview.Page} The page to be removed.
*/
remove(page) {
var pane = this.getChildControl("pane");
var bar = this.getChildControl("bar");
var button = page.getButton();
var children = pane.getChildren();
// Try to select next page
if (this.getSelection()[0] == page) {
var index = children.indexOf(page);
if (index == 0) {
if (children[1]) {
this.setSelection([children[1]]);
} else {
this.resetSelection();
}
} else {
this.setSelection([children[index - 1]]);
}
}
// Remove the button and page
bar.remove(button);
pane.remove(page);
// Remove the button from the radio group
this.__radioGroup.remove(button);
// Remove state from page
page.removeState(this.__barPositionToState[this.getBarPosition()]);
this.__updateFirstLastTabStates();
page.removeListener("close", this._onPageClose, this);
page.removeListener(
"changeTabVisibility",
this.__onPageChangeTabVisibility,
this
);
},
/**
* Returns TabView's children widgets.
*
* @return {qx.ui.tabview.Page[]} List of children.
*/
getChildren() {
return this.getChildControl("pane").getChildren();
},
/**
* Returns the position of the given page in the TabView.
*
* @param page {qx.ui.tabview.Page} The page to query for.
* @return {Integer} Position of the page in the TabView.
*/
indexOf(page) {
return this.getChildControl("pane").indexOf(page);
},
/**
* Returns the radio group manager.
*
* @return {qx.ui.form.RadioGroup} the radio group.
*/
getRadioGroup() {
return this.__radioGroup;
},
/*
---------------------------------------------------------------------------
APPLY ROUTINES
---------------------------------------------------------------------------
*/
/** @type {Map} Maps the bar position to an appearance state */
__barPositionToState: null,
/**
* Apply method for the placeBarOnTop-Property.
*
* Passes the desired value to the layout of the tabview so
* that the layout can handle it.
* It also sets the states to all buttons so they know the
* position of the bar.
*
* @param value {Boolean} The new value.
* @param old {Boolean} The old value.
*/
_applyBarPosition(value, old) {
var bar = this.getChildControl("bar");
var pane = this.getChildControl("pane");
var horizontal = value == "left" || value == "right";
var reversed = value == "right" || value == "bottom";
var layoutClass = horizontal ? qx.ui.layout.HBox : qx.ui.layout.VBox;
var layout = this._getLayout();
if (layout && layout instanceof layoutClass) {
// pass
} else {
this._setLayout((layout = new layoutClass()));
} // Update reversed
layout.setReversed(reversed); // Sync orientation to bar
bar.setOrientation(horizontal ? "vertical" : "horizontal"); // Read children
var children = this.getChildren();
var i, l;
// Toggle state to bar
if (old) {
var oldState = this.__barPositionToState[old];
// Update bar
bar.removeState(oldState);
// Update pane
pane.removeState(oldState);
// Update pages
for (i = 0, l = children.length; i < l; i++) {
children[i].removeState(oldState);
}
}
if (value) {
var newState = this.__barPositionToState[value];
// Update bar
bar.addState(newState);
// Update pane
pane.addState(newState);
// Update pages
for (i = 0, l = children.length; i < l; i++) {
children[i].addState(newState);
}
}
},
/*
---------------------------------------------------------------------------
SELECTION API
---------------------------------------------------------------------------
*/
/**
* Returns an array of currently selected items.
*
* Note: The result is only a set of selected items, so the order can
* differ from the sequence in which the items were added.
*
* @return {qx.ui.tabview.Page[]} List of items.
*/
getSelection() {
var buttons = this.__radioGroup.getSelection();
var result = [];
for (var i = 0; i < buttons.length; i++) {
result.push(buttons[i].getUserData("page"));
}
return result;
},
/**
* Replaces current selection with the given items.
*
* @param items {qx.ui.tabview.Page[]} Items to select.
* @throws {Error} if one of the items is not a child element and if
* items contains more than one elements.
*/
setSelection(items) {
var buttons = [];
for (var i = 0; i < items.length; i++) {
buttons.push(items[i].getChildControl("button"));
}
this.__radioGroup.setSelection(buttons);
},
/**
* Clears the whole selection at once.
*/
resetSelection() {
this.__radioGroup.resetSelection();
},
/**
* Detects whether the given item is currently selected.
*
* @param item {qx.ui.tabview.Page} Any valid selectable item.
* @return {Boolean} Whether the item is selected.
* @throws {Error} if one of the items is not a child element.
*/
isSelected(item) {
var button = item.getChildControl("button");
return this.__radioGroup.isSelected(button);
},
/**
* Whether the selection is empty.
*
* @return {Boolean} Whether the selection is empty.
*/
isSelectionEmpty() {
return this.__radioGroup.isSelectionEmpty();
},
/**
* Returns all elements which are selectable.
*
* @return {qx.ui.tabview.Page[]} The contained items.
* @param all {Boolean} true for all selectables, false for the
* selectables the user can interactively select
*/
getSelectables(all) {
var buttons = this.__radioGroup.getSelectables(all);
var result = [];
for (var i = 0; i < buttons.length; i++) {
result.push(buttons[i].getUserData("page"));
}
return result;
},
/**
* Event handler for <code>changeSelection</code>.
*
* @param e {qx.event.type.Data} Data event.
*/
_onChangeSelection(e) {
var pane = this.getChildControl("pane");
var button = e.getData()[0];
var oldButton = e.getOldData()[0];
var value = [];
var old = [];
if (button) {
value = [button.getUserData("page")];
pane.setSelection(value);
this.scrollChildIntoView(button, null, null, false);
} else {
pane.resetSelection();
}
if (oldButton) {
old = [oldButton.getUserData("page")];
}
this.fireDataEvent("changeSelection", value, old);
},
/**
* Event handler for <code>beforeChangeSelection</code>.
*
* @param e {qx.event.type.Event} Data event.
*/
_onBeforeChangeSelection(e) {
if (
!this.fireNonBubblingEvent(
"beforeChangeSelection",
qx.event.type.Event,
[false, true]
)
) {
e.preventDefault();
}
},
/*
---------------------------------------------------------------------------
EVENT LISTENERS
---------------------------------------------------------------------------
*/
/**
* Event handler for the change of the selected item of the radio group.
* @param e {qx.event.type.Data} The data event
*/
_onRadioChangeSelection(e) {
var element = e.getData()[0];
if (element) {
this.setSelection([element.getUserData("page")]);
} else {
this.resetSelection();
}
},
/**
* Removes the Page widget on which the close button was tapped.
*
* @param e {qx.event.type.Pointer} pointer event
*/
_onPageClose(e) {
// reset the old close button states, before remove page
// see http://bugzilla.qooxdoo.org/show_bug.cgi?id=3763 for details
var page = e.getTarget();
var closeButton = page.getButton().getChildControl("close-button");
closeButton.reset();
this.remove(page);
},
__onPageChangeTabVisibility() {
this.__updateFirstLastTabStates();
},
__updateFirstLastTabStates() {
let firstPage = null;
let lastPage = null;
for (let pages = this.getChildren(), i = 0; i < pages.length; i++) {
let page = pages[i];
if (page.getTabVisibility() == "visible") {
if (!firstPage) {
firstPage = page;
}
lastPage = page;
}
page.removeState("firstTab");
page.removeState("lastTab");
}
firstPage?.addState("firstTab");
lastPage?.addState("lastTab");
}
},
/*
*****************************************************************************
DESTRUCTOR
*****************************************************************************
*/
destruct() {
this._disposeObjects("__radioGroup");
this.__barPositionToState = null;
}
});