diff options
| author | Dharmang Soni <dpr@openerp.com> | 2017-04-25 14:16:35 +0530 |
|---|---|---|
| committer | Aaron Bohy <aab@odoo.com> | 2017-09-25 14:10:40 +0200 |
| commit | a12cfa8260a8a25930de2b842838588a27cdb734 (patch) | |
| tree | 397d0582ad24e76f1ea67014d88c62a20f20f1d7 /addons/web/static/src | |
| parent | 39e83e84c7622d4d33e746798a45065494563143 (diff) | |
[IMP] web: grouped Kanban: UX improvements in mobile
This commit aims to improve the UX of grouped Kanban views on
mobile devices. Only one column is now displayed, full width. The
columns' records are lazy-loaded. The user can swipe horizontally
to navigate through columns (thanks to the touchSwipe library).
Diffstat (limited to 'addons/web/static/src')
| -rw-r--r-- | addons/web/static/src/js/views/kanban/kanban_column.js | 2 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/kanban/kanban_controller.js | 15 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/kanban/kanban_model.js | 12 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/kanban/kanban_renderer.js | 115 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/kanban/kanban_view.js | 18 | ||||
| -rw-r--r-- | addons/web/static/src/less/kanban_view.less | 3 | ||||
| -rw-r--r-- | addons/web/static/src/less/kanban_view_mobile.less | 124 | ||||
| -rw-r--r-- | addons/web/static/src/xml/kanban.xml | 10 |
8 files changed, 292 insertions, 7 deletions
diff --git a/addons/web/static/src/js/views/kanban/kanban_column.js b/addons/web/static/src/js/views/kanban/kanban_column.js index dac28c536dd..ed8ffe1d4ed 100644 --- a/addons/web/static/src/js/views/kanban/kanban_column.js +++ b/addons/web/static/src/js/views/kanban/kanban_column.js @@ -238,7 +238,7 @@ var KanbanColumn = Widget.extend({ this.$header.find('.o_column_title').text(title); this.$header.find('.o-kanban-count').text(this.records.length); - this.$el.toggleClass('o_column_folded', this.folded); + this.$el.toggleClass('o_column_folded', this.folded && !config.isMobile); var tooltip = this.size + _t(' records'); tooltip = '<p>' + tooltip + '</p>' + this.tooltipInfo; this.$header.find('.o_kanban_header_title').tooltip({html: true}).attr('data-original-title', tooltip); diff --git a/addons/web/static/src/js/views/kanban/kanban_controller.js b/addons/web/static/src/js/views/kanban/kanban_controller.js index 8ba0e3fb7c3..1d07ad8c9f1 100644 --- a/addons/web/static/src/js/views/kanban/kanban_controller.js +++ b/addons/web/static/src/js/views/kanban/kanban_controller.js @@ -10,6 +10,7 @@ odoo.define('web.KanbanController', function (require) { var BasicController = require('web.BasicController'); var Context = require('web.Context'); var core = require('web.core'); +var config = require('web.config'); var view_dialogs = require('web.view_dialogs'); var _t = core._t; @@ -29,6 +30,7 @@ var KanbanController = BasicController.extend({ kanban_column_archive_records: '_onArchiveRecords', kanban_load_more: '_onLoadMore', column_toggle_fold: '_onToggleColumn', + kanban_load_records: '_onLoadColumnRecords' }), /** * @override @@ -232,6 +234,19 @@ var KanbanController = BasicController.extend({ }); }, /** + * @param {OdooRevent} event + */ + _onLoadColumnRecords: function (event) { + var self = this; + var column = event.target; + // Calling loadMore with offset override to zero + this.model.loadMore(column.db_id, 0).then(function (db_id) { + var data = self.model.get(db_id); + self.renderer.updateColumn(db_id, data); + }); + + }, + /** * @param {OdooEvent} event */ _onButtonClicked: function (event) { diff --git a/addons/web/static/src/js/views/kanban/kanban_model.js b/addons/web/static/src/js/views/kanban/kanban_model.js index 04ee7012bc4..4671df80d9f 100644 --- a/addons/web/static/src/js/views/kanban/kanban_model.js +++ b/addons/web/static/src/js/views/kanban/kanban_model.js @@ -7,6 +7,7 @@ odoo.define('web.KanbanModel', function (require) { */ var BasicModel = require('web.BasicModel'); +var config = require('web.config'); var KanbanModel = BasicModel.extend({ @@ -128,11 +129,12 @@ var KanbanModel = BasicModel.extend({ * Load more records in a group. * * @param {string} groupID localID of the group + * @param {integer} offset optional offset override * @returns {Deferred<string>} resolves to the localID of the group */ - loadMore: function (groupID) { + loadMore: function (groupID, offset) { var group = this.localData[groupID]; - var offset = group.loadMoreOffset + group.limit; + offset = offset === 0 ? 0 : group.loadMoreOffset + group.limit; return this.reload(group.id, { loadMoreOffset: offset, }); @@ -250,6 +252,12 @@ var KanbanModel = BasicModel.extend({ if (list.groupedBy.length > 1) { list.groupedBy = [list.groupedBy[0]]; } + // to prevent pre-loading data by default in mobile for kanban column + // by setting openGroupByDefault to 'false' which will set column's isOpen property + // to false and will ingore _load(). + if (config.isMobile) { + list.openGroupByDefault = false; + } return this._super.apply(this, arguments).then(function (result) { return self._readTooltipFields(list).then(_.constant(result)); }); diff --git a/addons/web/static/src/js/views/kanban/kanban_renderer.js b/addons/web/static/src/js/views/kanban/kanban_renderer.js index b46d1f7c5fd..ca64ae25fdf 100644 --- a/addons/web/static/src/js/views/kanban/kanban_renderer.js +++ b/addons/web/static/src/js/views/kanban/kanban_renderer.js @@ -3,6 +3,7 @@ odoo.define('web.KanbanRenderer', function (require) { var BasicRenderer = require('web.BasicRenderer'); var core = require('web.core'); +var config = require("web.config"); var KanbanColumn = require('web.KanbanColumn'); var KanbanRecord = require('web.KanbanRecord'); var quick_create = require('web.kanban_quick_create'); @@ -109,6 +110,9 @@ var KanbanRenderer = BasicRenderer.extend({ } this._setState(state); + // Used for mobile, when returning back to view + this.lastActiveMobileTab = -1; + this.isMobile = config.isMobile; }, //-------------------------------------------------------------------------- @@ -116,10 +120,12 @@ var KanbanRenderer = BasicRenderer.extend({ //-------------------------------------------------------------------------- /** - * Displays the quick create record in the first column. + * Displays the quick create record in the first column if not mobile otherwise, + * Uses active column index. */ addQuickCreate: function () { - this.widgets[0].addQuickCreate(); + var index = this.lastActiveMobileTab > -1 ? this.lastActiveMobileTab : 0; + this.widgets[index].addQuickCreate(); }, /** * Toggle fold/unfold the Column quick create widget @@ -145,11 +151,18 @@ var KanbanRenderer = BasicRenderer.extend({ * @returns {Deferred} */ updateColumn: function (localID, columnState) { + var self = this; var newColumn = new KanbanColumn(this, columnState, this.columnOptions, this.recordOptions); var index = _.findIndex(this.widgets, {db_id: localID}); var column = this.widgets[index]; this.widgets[index] = newColumn; - return newColumn.insertAfter(column.$el).then(column.destroy.bind(column)); + return newColumn.insertAfter(column.$el).then(function () { + column.destroy(); + if (self.isMobile) { + newColumn.$el.addClass("current"); + self._enableSwipeOnRecordGroup(newColumn); + } + }); }, /** * Updates a given record with its new state. @@ -201,6 +214,88 @@ var KanbanRenderer = BasicRenderer.extend({ this.createColumnEnabled || (this.state.groupedBy.length && this.state.data.length); }, + /* Enable swipe event on kanban column in mobile + * + * @private + * @param {KanbanColumn} column + */ + _enableSwipeOnRecordGroup: function (column) { + var self = this; + column.$el.swipe({ + swipeLeft: function() { + self._moveToGroup(++self.lastActiveMobileTab); + }, + swipeRight: function() { + self._moveToGroup(--self.lastActiveMobileTab); + } + }); + }, + /** + * Enables swipe on kanban columns + * + * @private + */ + _enableSwipeOnRecordGroups: function() { + var self = this; + this.$el.find(".o_kanban_group").swipe({ + swipeLeft: function() { + self._moveToGroup(++self.lastActiveMobileTab); + }, + swipeRight: function() { + self._moveToGroup(--self.lastActiveMobileTab); + } + }); + }, + /** + * Moving to kanan column group when swiping kanban column in mobile + * + * @private + * @param {integer} moveToIndex + */ + _moveToGroup: function (moveToIndex) { + var self = this; + if (this.widgets.length - 1 < moveToIndex) { + this.lastActiveMobileTab = this.widgets.length - 1; + return; + } else if (moveToIndex < 0) { + this.lastActiveMobileTab = 0; + return; + } + this.lastActiveMobileTab = moveToIndex; + var moveTo = moveToIndex; + var next = moveToIndex + 1; + var previous = moveToIndex - 1; + this.$(".o_kanban_group").removeClass("previous next current before after"); + this.$(".o_kanban_mobile_tab").removeClass("previous next current before after"); + _.each(this.widgets, function(column, index) { + var recordPane = self.$(".o_kanban_group[data-id=" + column.id + "]"); + var tab = self.$(".o_kanban_mobile_tab[data-id=" + column.id + "]"); + if (index == previous) { + tab.addClass("previous"); + tab.css("margin-left", "-" + (tab.outerWidth() / 2) + "px"); + recordPane.addClass("previous"); + } else if (index == next) { + tab.addClass("next"); + tab.css("margin-left", "-" + (tab.outerWidth() / 2) + "px"); + recordPane.addClass("next"); + } else if (index < moveTo) { + tab.addClass("before"); + tab.css("margin-left", "-" + tab.outerWidth() + "px"); + recordPane.addClass("before"); + } else if (index == moveTo) { + var marginLeft = tab.outerWidth() / 2; + tab.css("margin-left", "-" + marginLeft + "px"); + tab.addClass("current"); + recordPane.addClass("current"); + if (column.isEmpty()) + column.trigger_up('kanban_load_records'); + } else if (index > moveTo) { + tab.addClass("after"); + tab.css("margin-left", "0"); + recordPane.addClass("after"); + } + }); + }, /** * Renders empty invisible divs in a document fragment. * @@ -224,6 +319,12 @@ var KanbanRenderer = BasicRenderer.extend({ var self = this; // Render columns + if (this.isMobile) { + this.$el.append($(qweb.render("KanbanView.MobileTabs", {'data': this.state.data}))); + this.$(".o_kanban_mobile_tab").on('click', function (event) { + self._moveToGroup($(event.currentTarget).index()); + }); + } _.each(this.state.data, function (group) { var column = new KanbanColumn(self, group, self.columnOptions, self.recordOptions); if (!group.value) { @@ -306,6 +407,14 @@ var KanbanRenderer = BasicRenderer.extend({ this._renderUngrouped(fragment); } this.$el.append(fragment); + if (this.widgets && config.isMobile && isGrouped) { + core.bus.on("DOM_updated", this, function() { + this._enableSwipeOnRecordGroups(); + this._moveToGroup(this.lastActiveMobileTab); + }); + // Required to allow mobile kanban stage tabs to enable swipe and move to active tab + core.bus.trigger('DOM_updated'); + } } return this._super.apply(this, arguments).then(_.invoke.bind(_, oldWidgets, 'destroy')); diff --git a/addons/web/static/src/js/views/kanban/kanban_view.js b/addons/web/static/src/js/views/kanban/kanban_view.js index 3e808de72e3..41edfc1a09b 100644 --- a/addons/web/static/src/js/views/kanban/kanban_view.js +++ b/addons/web/static/src/js/views/kanban/kanban_view.js @@ -3,6 +3,7 @@ odoo.define('web.KanbanView', function (require) { var BasicView = require('web.BasicView'); var core = require('web.core'); +var config = require('web.config'); var KanbanModel = require('web.KanbanModel'); var KanbanRenderer = require('web.KanbanRenderer'); var KanbanController = require('web.KanbanController'); @@ -20,6 +21,7 @@ var KanbanView = BasicView.extend({ Model: KanbanModel, Controller: KanbanController, Renderer: KanbanRenderer, + js_libs: [] }, viewType: 'kanban', @@ -96,7 +98,21 @@ var KanbanView = BasicView.extend({ return JSON.parse(viewInfo.arch.attrs.quick_create); } return true; - } + }, + /** + * Adding touch swipe library for mobile + * + * @override + * @private + */ + _loadLibs: function () { + if (config.isMobile) { + this.config.js_libs.push("/web/static/lib/jquery.touchSwipe/jquery.touchSwipe.js"); + } + return this._super.apply(this, arguments); + }, }); + return KanbanView; + }); diff --git a/addons/web/static/src/less/kanban_view.less b/addons/web/static/src/less/kanban_view.less index 33baa09cc33..0dc06de9f0b 100644 --- a/addons/web/static/src/less/kanban_view.less +++ b/addons/web/static/src/less/kanban_view.less @@ -174,6 +174,9 @@ border-color: @gray-lighter-darker; min-width: 150px; margin: 0 0 -1px; + @media (max-width: @screen-xs-max) { + margin: 0px; + } // ------- Kanban Record, v11 Layout ------- // Records colours diff --git a/addons/web/static/src/less/kanban_view_mobile.less b/addons/web/static/src/less/kanban_view_mobile.less new file mode 100644 index 00000000000..a14c4b8bcfc --- /dev/null +++ b/addons/web/static/src/less/kanban_view_mobile.less @@ -0,0 +1,124 @@ +@o-kanban-mobile-tabs-height: 40px; + +.o_kanban_view { + + .o_kanban_mobile_tabs { + display: none; + } + + @media (max-width: @screen-xs-max){ + overflow: hidden; + display: block; + position: relative; + + &.o_kanban_grouped { + padding: 0px; + + .o_column_quick_create { + display: none; + } + + .o_kanban_mobile_tabs { + display: block; + position: relative; + overflow: hidden; + height: @o-kanban-mobile-tabs-height; + background: #5E5E5E; + + .o_kanban_mobile_tab { + display: block; + position: absolute; + font-size: 14px; + font-weight: 500; + width: auto; + left: 100%; + padding: 10px; + color: #fff; + text-align: center; + height: @o-kanban-mobile-tabs-height; + transition: 0.2s all; + transform: translate3d(0,0,0); + + .o_column_title { + display: block; + white-space: nowrap; + } + + &:after { + content: ' '; + position: absolute; + bottom: 0px; + left: 0; + background: #ffffff; + width: 100%; + height: 3px; + opacity: 0; + transition: 0.2s all; + transform: translate3d(0,3px,0); + } + + &.previous, &.before { + left: 0px; + } + + &.current { + left: 50%; + font-weight: 600; + + &:after { + opacity: 1; + transform: translate3d(0,0,0); + } + } + + &.next, &.after { + left: 100%; + } + } + } + + &.o_kanban_small_column .o_kanban_group { + &:not(.o_column_folded) { + overflow-x: scroll; + width: 100%; + height: 100%; + margin-top: @o-kanban-mobile-tabs-height; + padding-bottom: @o-kanban-mobile-tabs-height; + padding-top: 8px; + position: absolute; + top: 0; + left: 0; + transition: 0.2s all; + transform: translate3d(0,0,0); + + .o_kanban_header { + display: none; + } + .o_kanban_record, .o_kanban_quick_create { + width: auto; + border-radius: 2px; + padding-top: 10px; + padding-bottom: 10px; + margin-top: 5px; + margin-bottom: 5px; + } + .o_kanban_quick_create { + padding: 8px; + } + + &.previous { + left: -100%; + } + + &.current, &.before { + left: 0; + } + + &.next, &.after { + left: 100%; + } + } + } + } + } +}
\ No newline at end of file diff --git a/addons/web/static/src/xml/kanban.xml b/addons/web/static/src/xml/kanban.xml index 5e5123c2bdc..ed4c9fc3772 100644 --- a/addons/web/static/src/xml/kanban.xml +++ b/addons/web/static/src/xml/kanban.xml @@ -8,6 +8,16 @@ </div> </t> +<t t-name="KanbanView.MobileTabs"> + <div class="o_kanban_mobile_tabs"> + <t t-foreach="data" t-as="group"> + <div class="o_kanban_mobile_tab" t-att-data-id="group.res_id"> + <span class="o_column_title"><t t-esc="group.value"/></span> + </div> + </t> + </div> +</t> + <t t-name="KanbanView.Group"> <div class="o_kanban_group" t-att-data-id="widget.id"> <div class="o_kanban_header"> |
