diff --git a/addons/web/static/src/js/views/kanban/kanban_renderer_mobile.js b/addons/web/static/src/js/views/kanban/kanban_renderer_mobile.js index b5aa4945364..a9576e9836f 100644 --- a/addons/web/static/src/js/views/kanban/kanban_renderer_mobile.js +++ b/addons/web/static/src/js/views/kanban/kanban_renderer_mobile.js @@ -45,6 +45,7 @@ KanbanRenderer.include({ $column.scrollLeft(this._scrollPosition.left); $column.scrollTop(this._scrollPosition.top); } + this._computeTabPosition(); this._super.apply(this, arguments); }, /** @@ -103,24 +104,156 @@ KanbanRenderer.include({ // Private //-------------------------------------------------------------------------- + /** + * Update the columns positions + * + * @private + * @param {boolean} [animate=false] set to true to animate + */ + _computeColumnPosition: function (animate) { + if (this.widgets.length) { + var self = this; + var moveToIndex = this.activeColumnIndex; + var updateFunc = animate ? 'animate' : 'css'; + _.each(this.widgets, function (column, index) { + var columnID = column.id || column.db_id; + var $column = self.$('.o_kanban_group[data-id="' + columnID + '"]'); + if (index === moveToIndex - 1) { + $column[updateFunc]({left: '-100%'}); + } else if (index === moveToIndex + 1) { + $column[updateFunc]({left: '100%'}); + } else if (index === moveToIndex) { + $column[updateFunc]({left: '0%'}); + } else if (index < moveToIndex) { + $column.css({left: '-100%'}); + } else if (index > moveToIndex) { + $column.css({left: '100%'}); + } + }); + } + }, + + /** + * Define the o_current class to the current selected kanban (column & tab) + * + * @private + */ + _computeCurrentColumn: function () { + if (this.widgets.length) { + var column = this.widgets[this.activeColumnIndex]; + if (!column) { + return; + } + var columnID = column.id || column.db_id; + this.$('.o_kanban_mobile_tab.o_current, .o_kanban_group.o_current') + .removeClass('o_current'); + this.$('.o_kanban_group[data-id="' + columnID + '"], ' + + '.o_kanban_mobile_tab[data-id="' + columnID + '"]') + .addClass('o_current'); + } + }, + + /** + * Update the tabs positions + * + * @private + */ + _computeTabPosition: function () { + this._computeTabJustification(); + this._computeTabScrollPosition(); + }, + + /** + * Update the tabs positions + * + * @private + */ + _computeTabScrollPosition: function () { + if (this.widgets.length) { + var lastItemIndex = this.widgets.length - 1; + var moveToIndex = this.activeColumnIndex; + var scrollToLeft = 0; + for (var i = 0; i < moveToIndex; i++) { + var columnWidth = this._getTabWidth(this.widgets[i]); + // apply + if (moveToIndex !== lastItemIndex && i === moveToIndex - 1) { + var partialWidth = 0.75; + scrollToLeft += columnWidth * partialWidth; + } else { + scrollToLeft += columnWidth; + } + } + // Apply the scroll x on the tabs + this.$('.o_kanban_mobile_tabs').scrollLeft(scrollToLeft); + } + }, + + /** + * Compute the justify content of the kanban tab headers + * + * @private + */ + _computeTabJustification: function () { + if (this.widgets.length) { + var self = this; + // Use to compute the sum of the width of all tab + var widthChilds = this.widgets.reduce(function (total, column) { + return total + self._getTabWidth(column); + }, 0); + // Apply a space around between child if the parent length is higher then the sum of the child width + var $tabs = this.$('.o_kanban_mobile_tabs'); + $tabs.toggleClass('justify-content-around', $tabs.outerWidth() >= widthChilds); + } + }, + /** * Enables swipe event on the current column * * @private - * @param {KanbanColumn} column */ _enableSwipe: function () { var self = this; this.$el.swipe({ excludedElements: ".o_kanban_mobile_tabs", swipeLeft: function () { - self._moveToGroup(self.activeColumnIndex + 1, self.ANIMATE); + var moveToIndex = self.activeColumnIndex + 1; + if (moveToIndex < self.widgets.length) { + self._moveToGroup(moveToIndex, self.ANIMATE); + } }, swipeRight: function () { - self._moveToGroup(self.activeColumnIndex - 1, self.ANIMATE); + var moveToIndex = self.activeColumnIndex - 1; + if (moveToIndex > -1) { + self._moveToGroup(moveToIndex, self.ANIMATE); + } } }); }, + + /** + * Retrieve the outerWidth of a given widget column + * + * @param {KanbanColumn} column + * @returns {integer} outerWidth of the found column + * @private + */ + _getTabWidth : function (column) { + var columnID = column.id || column.db_id; + return this.$('.o_kanban_mobile_tab[data-id="' + columnID + '"]').outerWidth(); + }, + + /** + * Update the kanban layout + * + * @private + * @param {boolean} [animate=false] set to true to animate + */ + _layoutUpdate : function (animate) { + this._computeCurrentColumn(); + this._computeTabPosition(); + this._computeColumnPosition(animate); + }, + /** * Moves to the given kanban column * @@ -133,6 +266,7 @@ KanbanRenderer.include({ _moveToGroup: function (moveToIndex, animate) { var self = this; if (moveToIndex < 0 || moveToIndex >= this.widgets.length) { + this._layoutUpdate(animate); return $.when(); } var def = $.Deferred(); @@ -141,36 +275,13 @@ KanbanRenderer.include({ this.trigger_up('kanban_load_records', { columnID: column.db_id, onSuccess: function () { - // update the columns and tabs positions (optionally with an animation) - var updateFunc = animate ? 'animate' : 'css'; - self.$('.o_kanban_mobile_tab, .o_kanban_group').removeClass('o_current'); - _.each(self.widgets, function (column, index) { - var columnID = column.id || column.db_id; - var $column = self.$('.o_kanban_group[data-id="' + columnID + '"]'); - var $tab = self.$('.o_kanban_mobile_tab[data-id="' + columnID + '"]'); - if (index === moveToIndex - 1) { - $column[updateFunc]({left: '-100%'}); - $tab[updateFunc]({left: '0%'}); - } else if (index === moveToIndex + 1) { - $column[updateFunc]({left: '100%'}); - $tab[updateFunc]({left: '100%'}); - } else if (index === moveToIndex) { - $column[updateFunc]({left: '0%'}); - $tab[updateFunc]({left: '50%'}); - $tab.add($column).addClass('o_current'); - } else if (index < moveToIndex) { - $column.css({left: '-100%'}); - $tab[updateFunc]({left: '-100%'}); - } else if (index > moveToIndex) { - $column.css({left: '100%'}); - $tab[updateFunc]({left: '200%'}); - } - }); + self._layoutUpdate(animate); def.resolve(); }, }); return def; }, + /** * @override * @private @@ -193,6 +304,7 @@ KanbanRenderer.include({ })).prependTo(fragment); return result; }, + /** * @override * @private @@ -201,7 +313,8 @@ KanbanRenderer.include({ var self = this; return this._super.apply(this, arguments).then(function () { if (self.state.groupedBy.length) { - return self._moveToGroup(self.activeColumnIndex); + // force first column for kanban view, because the groupedBy can be changed + return self._moveToGroup(0); } }); }, diff --git a/addons/web/static/src/scss/kanban_view_mobile.scss b/addons/web/static/src/scss/kanban_view_mobile.scss index 53dc200f398..e464fd88b8a 100644 --- a/addons/web/static/src/scss/kanban_view_mobile.scss +++ b/addons/web/static/src/scss/kanban_view_mobile.scss @@ -5,15 +5,16 @@ overflow: hidden; .o_kanban_mobile_tabs { + display:flex; + width: 100%; height: $o-kanban-mobile-tabs-height; position: relative; + overflow-x: auto; background-color: #5E5E5E; .o_kanban_mobile_tab { - @include o-position-absolute($left: 100%); - transform: translateX(-50%); height: $o-kanban-mobile-tabs-height; - padding: 10px; + padding: 10px 20px; font-size: 14px; color: white; @@ -23,6 +24,7 @@ } .o_column_title { white-space: nowrap; + text-transform: uppercase; } } } diff --git a/addons/web/static/tests/views/kanban_mobile_tests.js b/addons/web/static/tests/views/kanban_mobile_tests.js index a564ff7f8ed..cc8354432e3 100644 --- a/addons/web/static/tests/views/kanban_mobile_tests.js +++ b/addons/web/static/tests/views/kanban_mobile_tests.js @@ -56,7 +56,7 @@ QUnit.module('Views', { QUnit.module('KanbanView Mobile'); QUnit.test('mobile grouped rendering', function (assert) { - assert.expect(11); + assert.expect(13); var kanban = createView({ View: KanbanView, @@ -110,10 +110,16 @@ QUnit.module('Views', { }).get(); assert.deepEqual(column_ids, tab_ids, "all columns data-id should match mobile tabs data-id"); + // kanban tabs with tab with lower width then available with have justify-content-around class + assert.containsN(kanban, '.o_kanban_mobile_tabs.justify-content-around', 1, + "should have justify-content-around class"); + assert.hasClass(kanban.$('.o_kanban_mobile_tabs'), 'justify-content-around', + "the mobile tabs have the class 'justify-content-around'"); + kanban.destroy(); }); QUnit.test('mobile grouped with undefined column', function (assert) { - assert.expect(3); + assert.expect(5); var kanban = createView({ View: KanbanView, @@ -141,10 +147,16 @@ QUnit.module('Views', { }).get(); assert.deepEqual(column_ids, tab_ids, "all columns data-id should match mobile tabs data-id"); + // kanban tabs with tab with lower width then available with have justify-content-around class + assert.containsN(kanban, '.o_kanban_mobile_tabs.justify-content-around', 1, + "should have justify-content-around class"); + assert.hasClass(kanban.$('.o_kanban_mobile_tabs'), 'justify-content-around', + "the mobile tabs have the class 'justify-content-around'"); + kanban.destroy(); }); QUnit.test('mobile grouped on many2one rendering', function (assert) { - assert.expect(3); + assert.expect(5); var kanban = createView({ View: KanbanView, @@ -172,6 +184,12 @@ QUnit.module('Views', { }).get(); assert.deepEqual(column_ids, tab_ids, "all columns data-id should match mobile tabs data-id"); + // kanban tabs with tab with lower width then available with have justify-content-around class + assert.containsN(kanban, '.o_kanban_mobile_tabs.justify-content-around', 1, + "should have justify-content-around class"); + assert.hasClass(kanban.$('.o_kanban_mobile_tabs'), 'justify-content-around', + "the mobile tabs have the class 'justify-content-around'"); + kanban.destroy(); });