@openui5/sap.m
Version:
OpenUI5 UI Library sap.m
583 lines (506 loc) • 19.6 kB
JavaScript
/*!
* UI development toolkit for HTML5 (OpenUI5)
* (c) Copyright 2009-2022 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
// Provides control sap.m.TabContainer.
sap.ui.define([
'./library',
'sap/ui/core/Control',
'sap/ui/core/IconPool',
'./TabContainerRenderer'
],
function(library, Control, IconPool, TabContainerRenderer) {
"use strict";
// shortcut for sap.m.ButtonType
var ButtonType = library.ButtonType;
/**
* Constructor for a new <code>TabContainer</code>.
*
* @param {string} [sId] ID for the new control, generated automatically if no ID is given
* @param {object} [mSettings] Initial settings for the new control
*
* @class
* A container control for managing multiple tabs, allowing the user to open and edit different items simultaneously.
*
* <h3>Overview</h3>
*
* The control contains a <code>TabStrip</code> area where the user can choose which tab to view/edit.
* When the open tabs are more than what can be displayed on the screen, there is an overflow mechanism.
* To access the tabs hidden in the overflow area, the user has to either use the overflow button (left or right arrow)
* to scroll them horizontally or the overflow overview button (down arrow) and view all open items as a list.
*
* Each tab has a title and a <i>Close Tab</i> button. The title is truncated, if it's longer than 25 characters.
* On desktop, the <i>Close Tab</i> button is displayed on the currently active tab and for the other tabs it appears on mouse hover.
* On mobile devices, the <i>Close Tab</i> buttons are always visible.
*
* To show that the open items have unsaved changes, the corresponding tabs can display an asterisk (*) after the title
* as a visual indication that the item is not saved. This is managed by the app developer using
* {@link sap.m.TabContainerItem TabContainerItem}'s <code>modified</code> property.
*
* <h3>Usage</h3>
*
* The <code>TabContainer</code> can have an <i>Add New Tab</i> button, which appears as a '+' icon on the
* top-right area of the control. When the user clicks or taps this button, the <code>addNewButtonPress</code> event is fired.
*
* <h3>Responsive behavior</h3>
*
* The <code>TabContainer</code> is a full-page container that takes 100% of its parent width and height.
* As the control is expected to occupy the whole parent, it should be the only child of its parent.
*
* @extends sap.ui.core.Control
*
* @author SAP SE
* @version 1.60.39
*
* @constructor
* @public
* @since 1.34
* @alias sap.m.TabContainer
* @ui5-metamodel This control/element also will be described in the UI5 (legacy) designtime metamodel
*/
var TabContainer = Control.extend("sap.m.TabContainer", /** @lends sap.m.TabContainer.prototype */ {
metadata : {
library : "sap.m",
properties : {
/**
* Defines whether an <i>Add New Tab</i> button is displayed in the <code>TabStrip</code>.
*/
showAddNewButton : {type : "boolean", group : "Misc", defaultValue : false}
},
aggregations : {
/**
* The items displayed in the <code>TabContainer</code>.
*/
items : {type : "sap.m.TabContainerItem", multiple : true, singularName: "item", bindable: "bindable"},
/**
* The <i>Add New Tab</i> button displayed in the <code>TabStrip</code>.
*/
_addNewButton : {type : "sap.m.Button", multiple : false, visibility : "hidden"},
/**
* Internal aggregation for managing the tab elements.
*/
_tabStrip : {type : "sap.ui.core.Control", multiple : false, visibility : "hidden"}
},
associations : {
/**
* Sets or retrieves the selected item from the <code>items</code> aggregation.
*/
selectedItem : {type : "sap.m.TabContainerItem", multiple : false}
},
events : {
/**
* Fired when an item is closed.
*/
itemClose: {
allowPreventDefault: true,
parameters: {
/**
* The item to be closed.
*/
item: {type: "sap.m.TabContainerItem"}
}
},
/**
* Fired when an item is pressed.
*/
itemSelect: {
allowPreventDefault: true,
parameters: {
/**
* The selected item.
*/
item: { type: "sap.m.TabContainerItem" }
}
},
/**
* Fired when the <i>Add New Tab</i> button is pressed.
*/
addNewButtonPress: { }
},
designtime: "sap/m/designtime/TabContainer.designtime"
},
constructor : function (vId, mSettings) {
var aStashedItems = [];
// normalize the expected arguments
if (!mSettings && typeof vId === 'object') {
mSettings = vId;
}
/* Store the items for later and remove them for the initialization of the control to avoid racing
* condition with the initialization of the tab strip. This is only required when the items aggregation
* is initialized directly with an array of TabContainer items without data binding and a template. */
if (mSettings && Array.isArray(mSettings['items'])) {
aStashedItems = mSettings['items'];
delete mSettings['items'];
}
Control.prototype.constructor.apply(this, arguments);
var oControl = new sap.m.TabStrip(this.getId() + "--tabstrip", {
hasSelect: true,
itemSelect: function(oEvent) {
var oItem = oEvent.getParameter("item"),
oSelectedItem = this._fromTabStripItem(oItem);
this.setSelectedItem(oSelectedItem, oEvent);
}.bind(this),
itemClose: function(oEvent) {
var oItem = oEvent.getParameter("item"),
oRemovedItem = this._fromTabStripItem(oItem);
// prevent the tabstrip from closing the item by default
oEvent.preventDefault();
if (this.fireItemClose({item: oRemovedItem})) {
this.removeItem(oRemovedItem); // the tabstrip item will also get removed
}
}.bind(this)
});
this.setAggregation("_tabStrip", oControl, true);
if (mSettings && mSettings['showAddNewButton']) {
this.setShowAddNewButton(true);
}
// re-introduce any existing items from the constructor settings
aStashedItems.forEach(function (oItem) {
this.addItem(oItem);
}, this);
}
});
/* Contains mapping between TabContainerItem properties and TabStripItem properties,
that may be set via setter method */
var mTCItemToTSItemProperties = {
"name": "text",
"modified": "modified"
};
/**
* Called before the control is rendered.
*/
TabContainer.prototype.onBeforeRendering = function() {
if (this.getSelectedItem()) {
return;
}
this._setDefaultTab();
};
/**
* Lazy loads the control attached to the private <code>Add New Button</code> aggregation
* @returns {null | sap.m.Button} The <code>Add New Tab</code> button if present or null
* @private
*/
TabContainer.prototype._getAddNewTabButton = function() {
var oControl = this.getAggregation("_addNewButton");
var oRb = sap.ui.getCore().getLibraryResourceBundle("sap.m");
if (!oControl) {
oControl = new sap.m.Button({
type: ButtonType.Transparent,
tooltip: oRb.getText("TABCONTAINER_ADD_NEW_TAB"),
icon: IconPool.getIconURI("add"),
press: function() {
this.getParent().getParent().fireAddNewButtonPress();
}
});
oControl.addStyleClass("sapMTSAddNewTabBtn");
this.setAggregation("_addNewButton", oControl, true);
}
return oControl;
};
/**
* Gets a reference to the instance of the TabStrip aggregation.
*/
TabContainer.prototype._getTabStrip = function () {
return this.getAggregation("_tabStrip");
};
/**
* Finds a <code>TabContainerItem</code> corresponding to a given <code>TabStripItem</code>.
*
* @param {sap.m.TabStripItem} oItem <code>TabStripItem</code> instance, the corresponding <code>TabContainerItem</code> to be searched for
* @returns {sap.m.TabStripItem | null} The <code>TabContainerItem</code> found (if any)
* @private
*/
TabContainer.prototype._fromTabStripItem = function(oItem) {
var aItems = this.getItems() || [],
iItemsCount = aItems.length,
iIndex = 0;
for (; iIndex < iItemsCount; iIndex++) {
if (aItems[iIndex].getId() === oItem.getKey()) {
return aItems[iIndex];
}
}
return null;
};
/**
* Finds the <code>sap.m.TabStripItem</code> corresponding to a given <code>sap.m.TabContainerItem</code>.
*
* @param {sap.m.TabContainerItem | string} vItem object or ID of the <code>TabContainerItem</code>
* @returns {sap.m.TabStripItem | null} <code>TabStripItem</code> corresponding to a given <code>sap.m.TabContainerItem</code> (if any)
* @private
*/
TabContainer.prototype._toTabStripItem = function(vItem) {
var iIndex = 0,
sKey = vItem,
oTabStripItems,
oTabStripItemsCount,
oTabStrip = this._getTabStrip();
if (!oTabStrip) {
// resolves error /getItems() of null/ in case only the _tabStrip aggregation was for some reason removed/destroyed from the container
return null;
}
oTabStripItems = oTabStrip.getItems();
oTabStripItemsCount = oTabStripItems.length;
if (typeof vItem === "object") {
sKey = vItem.getId();
}
for (; iIndex < oTabStripItemsCount; iIndex++) {
if (oTabStripItems[iIndex].getKey() === sKey) {
return oTabStripItems[iIndex];
}
}
return null;
};
/**
* Gets the <code>TabContainerItem</code> content of the selected item if present.
* @returns { null | Array<sap.ui.core.Control> } The <code>TabContainerItem</code> content
* @private
*/
TabContainer.prototype._getSelectedItemContent = function() {
var oTabStrip = this._getTabStrip(),
sSelectedItem = this.getSelectedItem(),
oSelectedItem = sap.ui.getCore().byId(sSelectedItem),
oTabStripItem = this._toTabStripItem(oSelectedItem);
if (oTabStrip) {
// resolves error /getItems() of null/ in case only the _tabStrip aggregation was for some reason removed/destroyed from the container
oTabStrip.setSelectedItem(oTabStripItem);
}
return oSelectedItem ? oSelectedItem.getContent() : null;
};
/**
* Calculates the next item to be focused and selected and applies the focus and selection when an item is removed.
*
* @param {boolean} bSetAsSelected Whether the next item to be selected
* @private
*/
TabContainer.prototype._moveToNextItem = function (bSetAsSelected) {
if (!this._getTabStrip()._oItemNavigation) {
return;
}
var iItemsCount = this.getItems().length,
iCurrentFocusedIndex = this._getTabStrip()._oItemNavigation.getFocusedIndex(),
iNextIndex = iItemsCount === iCurrentFocusedIndex ? --iCurrentFocusedIndex : iCurrentFocusedIndex,
oNextItem = this.getItems()[iNextIndex],
fnFocusCallback = function () {
if (this._getTabStrip()._oItemNavigation) {
this._getTabStrip()._oItemNavigation.focusItem(iNextIndex);
}
};
// Selection (causes invalidation)
if (bSetAsSelected) {
this.setSelectedItem(oNextItem);
// Notify the subscriber
this.fireItemSelect({item: oNextItem});
}
// Focus (force to wait until invalidated)
setTimeout(fnFocusCallback.bind(this), 0);
};
TabContainer.prototype._attachItemPropertyChanged = function (oTabContainerItem) {
oTabContainerItem.attachItemPropertyChanged(function (oEvent) {
var sPropertyKey = oEvent['mParameters'].propertyKey;
if (mTCItemToTSItemProperties[sPropertyKey]) {//forward only if such property exists in TabStripItem
sPropertyKey = mTCItemToTSItemProperties[sPropertyKey];
var oTabStripItem = this._toTabStripItem(oEvent.getSource());
oTabStripItem && oTabStripItem.setProperty(sPropertyKey, oEvent['mParameters'].propertyValue, false);
}
}.bind(this));
};
/**
* Removes an item from the aggregation named <code>items</code>.
*
* @param {int | string | sap.m.TabContainerItem} vItem The item to remove or its index or ID
* @returns {sap.m.TabContainerItem} The removed item or null
* @public
*/
TabContainer.prototype.removeItem = function(vItem) {
var bIsSelected;
if (typeof vItem === "undefined" || vItem === null) {
return null;
}
//Remove the corresponding TabContainerItem
vItem = this.removeAggregation("items", vItem);
// The selection flag of the removed item
bIsSelected = vItem.getId() === this.getSelectedItem();
this._getTabStrip().removeItem(this._toTabStripItem(vItem));
// Perform selection switch
this._moveToNextItem(bIsSelected);
return vItem;
};
/**
* Overrides the method in order to handle propagation of item property changes to the <code>_tabStrip</code> instance copies.
*
* @param {string} sAggregationName Name of the added aggregation
* @param {object} oObject Instance that is going to be added
* @param {boolean} bSuppressInvalidate Flag indicating whether invalidation should be suppressed
* @returns {object} This instance for chaining
*/
TabContainer.prototype.addAggregation = function(sAggregationName, oObject, bSuppressInvalidate) {
if (sAggregationName === 'items') {
this._attachItemPropertyChanged(oObject);
}
return Control.prototype.addAggregation.call(this, sAggregationName, oObject, bSuppressInvalidate);
};
/**
* Overrides the method in order to handle propagation of item property changes to the <code>_tabStrip</code> instance copies.
*
* @param {string} sAggregationName Name of the added aggregation
* @param {object} oObject Instance that is going to be added
* @param {int} iIndex Index to insert the item
* @param {boolean} bSuppressInvalidate Flag indicating whether invalidation should be suppressed
* @returns {object} This instance for chaining
*/
TabContainer.prototype.insertAggregation = function(sAggregationName, oObject, iIndex, bSuppressInvalidate) {
if (sAggregationName === 'items') {
this._attachItemPropertyChanged(oObject);
}
return Control.prototype.insertAggregation.call(this, sAggregationName, oObject, iIndex, bSuppressInvalidate);
};
/*
* Adds a new <code>TabContainerItem</code> to the <code>items</code> aggregation of the <code>TabContainer</code>.
*
* @param {sap.m.TabContainerItem} oItem The new <code>TabContainerItem</code> to be added
* @returns {sap.m.TabContainerItem} The newly added <code>TabContainerItem</code>
* @override
*/
TabContainer.prototype.addItem = function(oItem) {
this.addAggregation("items", oItem, false);
this._getTabStrip().addItem(
new sap.m.TabStripItem({
key: oItem.getId(),
text: oItem.getName(),
modified: oItem.getModified()
})
);
return oItem;
};
/*
* Destroys all <code>TabContainerItem</code> entities from the <code>items</code> aggregation of the <code>TabContainer</code>.
*
* @returns {sap.m.TabContainer} This instance for chaining
* @override
*/
TabContainer.prototype.destroyItems = function() {
this._getTabStrip().destroyItems();
this.setAssociation("selectedItem", null);
return this.destroyAggregation("items");
};
/*
* Inserts a new <code>TabContainerItem</code> to the <code>items</code> aggregation of the <code>TabContainer</code> at a specified index.
*
* @param {sap.m.TabContainerItem} oItem The new <code>TabContainerItem</code> to be inserted
* @param {int} iIndex The index where the passed <code>TabContainerItem</code> to be inserted
* @returns {sap.m.TabContainer} This instance for chaining
* @override
*/
TabContainer.prototype.insertItem = function(oItem, iIndex) {
this._getTabStrip().insertItem(
new sap.m.TabStripItem({
key: oItem.getId(),
text: oItem.getName(),
modified: oItem.getModified()
}),
iIndex
);
return this.insertAggregation("items", oItem, iIndex);
};
/*
* Removes all <code>TabContainerItem</code> entities from the <code>items</code> aggregation of the <code>TabContainer</code>.
*
* @returns {sap.m.TabContainer} This instance for chaining
* @override
*/
TabContainer.prototype.removeAllItems = function() {
this._getTabStrip().removeAllItems();
this.setSelectedItem(null);
return this.removeAllAggregation("items");
};
/**
* Overrides the <code>addButton</code> property setter to proxy to the <code>TabStrip</code>.
*
* @param {sap.ui.core.Control} oButton The new control to be set as <code>TabStrip</code> <code>addButton</code> aggregation
* @returns {sap.m.TabContainer} This instance for chaining
* @override
*/
TabContainer.prototype.setAddButton = function (oButton) {
return this._getTabStrip().setAddButton(oButton);
};
/**
* Overrides the addButton property getter to proxy to the <code>TabStrip</code>.
*
* @returns {sap.ui.core.Control} The control assigned as a <code>TabStrip</code> addButton aggregation
* @override
*/
TabContainer.prototype.getAddButton = function () {
return this._getTabStrip().getAddButton();
};
/*
* Override <code>showAddNewButton</code> property setter to proxy to the <code>TabStrip</code>.
*
* @param {boolean} bShowButton Whether to show the <code>addNewButton</code>
* @returns {sap.m.TabContainer} <code>this</code> pointer for chaining
* @override
*/
TabContainer.prototype.setShowAddNewButton = function (bShowButton) {
this.setProperty("showAddNewButton", bShowButton, true);
var oTabStrip = this._getTabStrip();
if (oTabStrip) {
oTabStrip.setAddButton(bShowButton ? this._getAddNewTabButton() : null);
}
return this;
};
/*
* Override <code>selectedItem</code> property setter.
*
* @param {sap.m.TabContainerItem} oSelectedItem The new <code>TabContainerItem</code> to be selected
* @param {jQuery.Event} oEvent Event object that may be present when the selection change is bubbling
* @returns {sap.m.TabContainer} <code>this</code> pointer for chaining
* @override
*/
TabContainer.prototype.setSelectedItem = function (oSelectedItem, oEvent) {
/* As the 'setSelectedItem' might be part of a bubbling selection change event, allow the final event handler
* to prevent it. */
if (this.fireItemSelect({item: oSelectedItem})) {
var oTabStrip = this._getTabStrip();
if (oSelectedItem && oTabStrip) {
oTabStrip.setSelectedItem(this._toTabStripItem(oSelectedItem));
this._rerenderContent(oSelectedItem.getContent());
}
TabContainer.prototype.setAssociation.call(this, "selectedItem", oSelectedItem, true); //render manually;
return this;
}
if (oEvent) {
oEvent.preventDefault();
}
return this;
};
/**
* Re-renders only the displayed content.
* @private
* @param {Object} oContent The content, which should be rendered.
*/
TabContainer.prototype._rerenderContent = function(oContent) {
var $content = this.$("content"),
oRM;
if (!oContent || ($content.length <= 0)) {
return;
}
oRM = sap.ui.getCore().createRenderManager();
for (var i = 0; i < oContent.length; i++) {
oRM.renderControl(oContent[i]);
}
oRM.flush($content[0]);
oRM.destroy();
};
/**
* Sets the default selected item to the first item
*
* @returns {sap.m.TabStripItem|null}
* @private
*/
TabContainer.prototype._setDefaultTab = function() {
var oFirstItem = this.getItems()[0] || null;
this.setSelectedItem(oFirstItem);
return oFirstItem;
};
return TabContainer;
});