Merge pull request #507 from nextcloud/make-possible-to-remove-tabs-from-the-right-sidebar

Make possible to remove tabs from the right sidebar
This commit is contained in:
Joas Schilling 2017-11-29 12:40:51 +01:00 коммит произвёл GitHub
Родитель ae0433d13d c8b5d2798f
Коммит d3cdf41b97
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
2 изменённых файлов: 136 добавлений и 34 удалений

Просмотреть файл

@ -46,23 +46,24 @@
* The right sidebar is an area that can be shown or hidden from the right
* border of the document. It contains a view intended to provide details of
* the current call at the top and a TabView to which different sections can
* be added as needed. The call details view can be set through
* "setCallInfoView()" while new tabs can be added through "addTab()".
* be added and removed as needed. The call details view can be set through
* "setCallInfoView()" while new tabs can be added through "addTab()" and
* removed through "removeTab()".
*
* The SidebarView can be shown or hidden programatically using "show()" and
* "hide()". It will delegate on "OC.Apps.showAppSidebar()" and
* The SidebarView can be opened or closed programatically using "open()"
* and "close()". It will delegate on "OC.Apps.showAppSidebar()" and
* "OC.Apps.hideAppSidebar()", so it must be used along an "#app-content"
* that takes into account the "with-app-sidebar" CSS class.
*
* In order for the user to be able to show the sidebar when it is hidden,
* In order for the user to be able to open the sidebar when it is closed,
* the SidebarView shows a small icon ("#app-sidebar-trigger") on the right
* border of the document that shows the sidebar when clicked. When the
* sidebar is shown the icon is hidden.
* border of the document that opens the sidebar when clicked. When the
* sidebar is open the icon is hidden.
*
* By default the sidebar is disabled, that is, it is hidden and can not be
* shown, neither by the user nor programatically. Calling "enable()" will
* make possible for the sidebar to be shown, and calling "disable()" will
* prevent it again (also hidden it if it was shown).
* By default the sidebar is disabled, that is, it is closed and can not be
* opened, neither by the user nor programatically. Calling "enable()" will
* make possible for the sidebar to be opened, and calling "disable()" will
* prevent it again (also closing it if it was open).
*/
var SidebarView = Marionette.View.extend({
@ -142,7 +143,7 @@
/**
* Sets a new call info view.
*
* Once set, the Sidebar takes ownership of the view, and it will
* Once set, the SidebarView takes ownership of the view, and it will
* destroy it if a new one is set.
*
* @param Marionette.View callInfoView the view to set.
@ -167,8 +168,9 @@
* can provide an 'onRender' function to extend the default rendering of
* the header).
*
* The Sidebar takes ownership of the given content view, and it will
* destroy it when the Sidebar is destroyed.
* The SidebarView takes ownership of the given content view, and it
* will destroy it when the SidebarView is destroyed, except if the
* content view is removed first.
*
* @param string tabId the ID of the tab.
* @param Object tabHeaderOptions the options for the constructor of the
@ -180,6 +182,27 @@
this._tabView.addTab(tabId, tabHeaderOptions, tabContentView);
},
/**
* Removes the tab for the given tabId.
*
* If the tab to be removed is the one currently selected and there are
* other tabs the next one (in priority and then insertion order) is
* automatically selected; if the tab to be removed is the last one,
* then the previous one is selected instead. If there are no other tabs
* then the TabView is simply emptied.
*
* In any case the content view given when the tab was added is
* returned; this SidebarView will no longer have ownership of the
* content view, and thus the content view must be explicitly destroyed
* when no longer needed.
*
* @param string tabId the ID of the tab to remove.
* @return Marionette.View the content view of the removed tab.
*/
removeTab: function(tabId) {
return this._tabView.removeTab(tabId);
}
});
OCA.SpreedMe.Views.SidebarView = SidebarView;

Просмотреть файл

@ -76,9 +76,13 @@
// nothing to be rendered with a template.
template: _.noop,
childViewTriggers: {
// Propagate the event to the parent view.
'click:tabHeader': 'click:tabHeader'
childViewEvents: {
'click:tabHeader': 'selectTabHeader'
},
initialize: function() {
// The tabIds in priority and then insertion order.
this._tabIds = [];
},
addTabHeader: function(tabId, tabHeaderOptions) {
@ -95,6 +99,8 @@
var tabHeaderIndex = this._getIndexForTabHeaderPriority(tabHeaderOptions.priority);
this._tabIds.splice(tabHeaderIndex, 0, tabId);
// When adding a region and showing a view on it the target element
// of the region must exist in the parent view. Therefore, a dummy
// target element, which will be replaced with the tab header
@ -123,20 +129,11 @@
* @return int the insertion index.
*/
_getIndexForTabHeaderPriority: function(priority) {
// this.getRegions() returns an object that acts as a map, but it
// has no "length" property; _.map creates an array, thus ensuring
// that there is a "length" property to know the current number of
// tab headers.
var currentPriorities = _.map(this.getRegions(), function(region) {
return region.currentView.getOption('priority');
});
// By default sort() converts the values to strings and sorts them
// in ascending order using their Unicode value; a custom function
// must be used to sort them by their numerical value instead.
currentPriorities.sort(function(a, b) {
return a - b;
}).reverse();
// _.map creates an array, so "currentPriorities" will contain a
// "length" property.
var currentPriorities = _.map(this._tabIds, _.bind(function(tabId) {
return this.getRegion(tabId).currentView.getOption('priority');
}, this));
var index = _.findIndex(currentPriorities, function(currentPriority) {
return priority > currentPriority;
@ -149,6 +146,41 @@
return index;
},
/**
* Removes the tab header for the given tabId.
*
* If the tab header to be removed is the one currently selected and
* there are other tab headers the next one (in priority and then
* insertion order) is automatically selected; if the tab header to be
* removed is the last one, then the previous one is selected instead.
*
* @param string tabId the ID of the tab.
*/
removeTabHeader: function(tabId) {
var tabIdIndex = _.indexOf(this._tabIds, tabId);
// If the tab header to be removed is the one currently selected
// then select the next tab header or, if it is the last tab header,
// the previous one (or none if there are no other tab headers).
if (this._currentTabId === tabId) {
if (this._tabIds.length <= 1) {
delete this._currentTabId;
} else if (tabIdIndex === (this._tabIds.length - 1)) {
this.selectTabHeader(this._tabIds[tabIdIndex - 1]);
} else {
this.selectTabHeader(this._tabIds[tabIdIndex + 1]);
}
}
this._tabIds.splice(tabIdIndex, 1);
var removedRegion = this.removeRegion(tabId);
// Remove the dummy target element that was replaced by the view
// when it was shown and that is restored back when the region is
// removed.
removedRegion.el.remove();
},
selectTabHeader: function(tabId) {
if (this._currentTabId !== undefined) {
this.getChildView(this._currentTabId).setSelected(false);
@ -157,6 +189,8 @@
this._currentTabId = tabId;
this.getChildView(this._currentTabId).setSelected(true);
this.triggerMethod('select:tabHeader', tabId);
}
});
@ -208,7 +242,8 @@
* the header).
*
* The TabView takes ownership of the given content view, and it will
* destroy it when the TabView is destroyed.
* destroy it when the TabView is destroyed, except if the content view
* is removed first.
*
* @param string tabId the ID of the tab.
* @param Object tabHeaderOptions the options for the constructor of the
@ -231,8 +266,43 @@
}
},
onChildviewClickTabHeader: function(tabId) {
this.selectTab(tabId);
/**
* Removes the tab for the given tabId.
*
* If the tab to be removed is the one currently selected and there are
* other tabs the next one (in priority and then insertion order) is
* automatically selected; if the tab to be removed is the last one,
* then the previous one is selected instead. If there are no other tabs
* then this TabView is simply emptied.
*
* In any case the content view given when the tab was added is
* returned; this TabView will no longer have ownership of the content
* view, and thus the content view must be explicitly destroyed when no
* longer needed.
*
* @param string tabId the ID of the tab to remove.
* @return Marionette.View the content view of the removed tab.
*/
removeTab: function(tabId) {
if (!this._tabContentViews.hasOwnProperty(tabId)) {
return undefined;
}
var removedTabContentView = this._tabContentViews[tabId];
this._tabHeadersView.removeTabHeader(tabId);
delete this._tabContentViews[tabId];
// Removing the tab header selects a new tab header, which in turn
// changes the content view, except when there are no other tabs. In
// that case the content view would be being shown in the region and
// thus would have to be removed from there.
if (Object.keys(this._tabContentViews).length === 0) {
this.getRegion('tabContent').empty({preventDestroy: true});
}
return removedTabContentView;
},
/**
@ -246,7 +316,16 @@
}
this._tabHeadersView.selectTabHeader(tabId);
},
/**
* Shows the content view associated to the selected tab header.
*
* Only for internal use as an event handler.
*
* @param string tabId the ID of the selected tab.
*/
onChildviewSelectTabHeader: function(tabId) {
// With Marionette 3.1 "this.detachChildView('tabContent')" would be
// used instead of the "preventDestroy" option.
this.showChildView('tabContent', this._tabContentViews[tabId], { preventDestroy: true } );