Skip to content

Commit

Permalink
Merge pull request #1302 from nextcloud/fix-the-scroll-position-of-th…
Browse files Browse the repository at this point in the history
…e-chat-view-in-the-sidebar

Fix the scroll position of the chat view in the sidebar
  • Loading branch information
Ivansss authored Nov 20, 2018
2 parents 238ec46 + bc80148 commit d00f88f
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 11 deletions.
49 changes: 49 additions & 0 deletions js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,55 @@
guestNameModel: this._localStorageModel
});

// Focus the chat input when the chat tab is selected.
this._chatView.listenTo(this._sidebarView, 'select:tab', function(tabId) {
if (tabId !== 'chat') {
return;
}

this._chatView.focusChatInput();
}.bind(this));

// Opening and closing the sidebar detachs its contents to perform
// the animation; detaching an element and attaching it again resets
// its scroll position, so the scroll position of the chat view
// needs to be saved before the sidebar is closed and restored again
// once the sidebar is opened.
this._chatView.listenTo(this._sidebarView, 'opened', function() {
if (this._sidebarView.getCurrentTabId() !== 'chat') {
return;
}

this._chatView.restoreScrollPosition();
}.bind(this));
this._chatView.listenTo(this._sidebarView, 'close', function() {
if (this._sidebarView.getCurrentTabId() !== 'chat') {
return;
}

this._chatView.saveScrollPosition();
}.bind(this));

// Selecting a different tab detachs the contents of the previous
// tab and attachs the contents of the new tab; detaching an element
// and attaching it again resets its scroll position, so the scroll
// position of the chat view needs to be saved when the chat tab is
// unselected and restored again when the chat tab is selected.
this._chatView.listenTo(this._sidebarView, 'unselect:tab', function(tabId) {
if (tabId !== 'chat') {
return;
}

this._chatView.saveScrollPosition();
}.bind(this));
this._chatView.listenTo(this._sidebarView, 'select:tab', function(tabId) {
if (tabId !== 'chat') {
return;
}

this._chatView.restoreScrollPosition();
}.bind(this));

this._messageCollection.listenTo(roomChannel, 'leaveCurrentRoom', function() {
this.stopReceivingMessages();
});
Expand Down
6 changes: 3 additions & 3 deletions js/views/chatview.js
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@
return;
}

var scrollBottom = 0;
var scrollBottom = this.$container.scrollTop();

// When the last visible comment has a next sibling the scroll
// position is based on the top position of that next sibling.
Expand All @@ -348,9 +348,9 @@
// element (which would cause the next element to be fully shown
// if saving and restoring the scroll position again) due to
// rounding.
scrollBottom = this._getCommentTopPosition($nextSibling) - 1;
scrollBottom += this._getCommentTopPosition($nextSibling) - 1;
} else if (this._$lastVisibleComment.length > 0) {
scrollBottom = this._getCommentTopPosition(this._$lastVisibleComment) + this._getCommentOuterHeight(this._$lastVisibleComment);
scrollBottom += this._getCommentTopPosition(this._$lastVisibleComment) + this._getCommentOuterHeight(this._$lastVisibleComment);
}

this.$container.scrollTop(scrollBottom - this.$container.outerHeight());
Expand Down
47 changes: 42 additions & 5 deletions js/views/sidebarview.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,21 @@
* "setCallInfoView()" while new tabs can be added through "addTab()" and
* removed through "removeTab()".
*
* Tabs can be selected programatically using "selectTab()".
*
* No matter if it is done programatically or by the user, selecting a tab
* triggers the "select:tab" event with the ID of the tab as parameter;
* selecting a new tab deselects the current tab, so before "select:tab" is
* triggered "unselect:tab" is triggered with the ID of the previous tab.
*
* 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.
* and "close()".
*
* No matter if it is done programatically or by the user, opening the
* sidebar triggers the "open" and "opened" events, and closing
* the sidebar triggers the "close" and "closed" events; in both cases the
* first event is triggered when the animation starts and the second one
* when the animation ends.
*
* 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
Expand Down Expand Up @@ -81,6 +92,11 @@
'click @ui.sidebar a.close': 'close',
},

childViewTriggers: {
'unselect:tab': 'unselect:tab',
'select:tab': 'select:tab',
},

template: Handlebars.compile(TEMPLATE),

templateContext: {
Expand Down Expand Up @@ -139,13 +155,25 @@
return;
}

OC.Apps.showAppSidebar();
this.trigger('open');

this.getUI('sidebar').removeClass('disappear')
.show('slide', { direction: 'right' }, 300, function() {
this.trigger('opened');
}.bind(this));

this._open = true;
},

close: function() {
OC.Apps.hideAppSidebar();
this.trigger('close');

this.getUI('sidebar')
.hide('slide', { direction: 'right' }, 300, function() {
this.getUI('sidebar').addClass('disappear');

this.trigger('closed');
}.bind(this));

this._open = false;
},
Expand Down Expand Up @@ -201,6 +229,15 @@
this._tabView.selectTab(tabId);
},

/**
* Returns the ID of the currently selected tab.
*
* @return {string} the ID of the currently selected tab.
*/
getCurrentTabId: function() {
return this._tabView.getCurrentTabId();
},

/**
* Removes the tab for the given tabId.
*
Expand Down
28 changes: 25 additions & 3 deletions js/views/tabview.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@
},

selectTabHeader: function(tabId) {
this.triggerMethod('unselect:tabHeader', this._currentTabId);

if (this._currentTabId !== undefined) {
this.getChildView(this._currentTabId).setSelected(false);
}
Expand All @@ -192,6 +194,10 @@
this.getChildView(this._currentTabId).setSelected(true);

this.triggerMethod('select:tabHeader', tabId);
},

getCurrentTabId: function() {
return this._currentTabId;
}

});
Expand All @@ -202,6 +208,11 @@
* A TabView contains a set of tab headers and a content area. When a header
* is selected its associated content view is shown in the content area;
* otherwise its content is hidden (although the header is always shown).
*
* Selecting a tab triggers the "select:tab" event with the ID of the tab as
* parameter; selecting a new tab deselects the current tab, so before
* "select:tab" is triggered "unselect:tab" is triggered with the ID of the
* previous tab.
*/
var TabView = Marionette.View.extend({

Expand All @@ -213,6 +224,10 @@
tabContent: '.tab'
},

childViewTriggers: {
'unselect:tabHeader': 'unselect:tab',
},

template: Handlebars.compile(TEMPLATE_TAB_VIEW),

initialize: function() {
Expand Down Expand Up @@ -319,6 +334,15 @@
this._tabHeadersView.selectTabHeader(tabId);
},

/**
* Returns the ID of the currently selected tab.
*
* @return {string} the ID of the currently selected tab.
*/
getCurrentTabId: function() {
return this._tabHeadersView.getCurrentTabId();
},

/**
* Shows the content view associated to the selected tab header.
*
Expand All @@ -338,9 +362,7 @@
this._selectedTabExtraClass = 'tab-' + tabId;
this.getRegion('tabContent').$el.addClass(this._selectedTabExtraClass);

if (tabId === 'chat') {
this._tabContentViews[tabId].focusChatInput();
}
this.triggerMethod('select:tab', tabId);
}

});
Expand Down

0 comments on commit d00f88f

Please sign in to comment.