summaryrefslogtreecommitdiff
path: root/addons/web/static/src
diff options
context:
space:
mode:
authorDharmang Soni <dpr@openerp.com>2017-04-25 14:16:35 +0530
committerAaron Bohy <aab@odoo.com>2017-09-25 14:10:40 +0200
commita12cfa8260a8a25930de2b842838588a27cdb734 (patch)
tree397d0582ad24e76f1ea67014d88c62a20f20f1d7 /addons/web/static/src
parent39e83e84c7622d4d33e746798a45065494563143 (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.js2
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_controller.js15
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_model.js12
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_renderer.js115
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_view.js18
-rw-r--r--addons/web/static/src/less/kanban_view.less3
-rw-r--r--addons/web/static/src/less/kanban_view_mobile.less124
-rw-r--r--addons/web/static/src/xml/kanban.xml10
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">