[IMP] web: Calendar: mobile UX improvements

The following tweaks have been applied on mobile devices:

 - remove left and right arrow in control panel
 - enable swipe navigation (instead of left/right arrows)
 - replace 'Today' button by a calendar icon
 - remove static text 'Attendees:' in events
 - shorten header content in week mode to prevent them from
   overlapping
 - ensure that the calendar fits in the screen

Also introduce a mobile test suite for the calendar view.

Task #31449
This commit is contained in:
Suraj Shukla
2018-02-27 12:18:55 +01:00
committed by Aaron Bohy
parent d36695cbc7
commit ebe58d9267
8 changed files with 169 additions and 31 deletions
@@ -10,6 +10,7 @@ odoo.define('web.CalendarController', function (require) {
*/
var AbstractController = require('web.AbstractController');
var config = require('web.config');
var core = require('web.core');
var Dialog = require('web.Dialog');
var dialogs = require('web.view_dialogs');
@@ -23,8 +24,10 @@ var CalendarController = AbstractController.extend({
changeDate: '_onChangeDate',
changeFilter: '_onChangeFilter',
dropRecord: '_onDropRecord',
next: '_onNext',
openCreate: '_onOpenCreate',
openEvent: '_onOpenEvent',
prev: '_onPrev',
quickCreate: '_onQuickCreate',
toggleFullWidth: '_onToggleFullWidth',
updateRecord: '_onUpdateRecord',
@@ -73,15 +76,16 @@ var CalendarController = AbstractController.extend({
*/
renderButtons: function ($node) {
var self = this;
this.$buttons = $(QWeb.render("CalendarView.buttons", {'widget': this}));
this.$buttons = $(QWeb.render('CalendarView.buttons', {
isMobile: config.device.isMobile,
}));
this.$buttons.on('click', 'button.o_calendar_button_new', function () {
self.trigger_up('switch_view', {view_type: 'form'});
});
_.each(['prev', 'today', 'next'], function (action) {
self.$buttons.on('click', '.o_calendar_button_' + action, function () {
self.model[action]();
self.reload();
self._move(action);
});
});
_.each(['day', 'week', 'month'], function (scale) {
@@ -105,6 +109,18 @@ var CalendarController = AbstractController.extend({
//--------------------------------------------------------------------------
/**
* Move to the requested direction and reload the view
*
* @private
* @param {string} to either 'prev', 'next' or 'today'
* @returns {Deferred}
*/
_move: function (to) {
this.model[to]();
return this.reload();
},
/**
* @private
* @param {Object} record
* @param {integer} record.id
* @returns {Deferred}
@@ -156,6 +172,14 @@ var CalendarController = AbstractController.extend({
_onDropRecord: function (event) {
this._updateRecord(event.data);
},
/**
* @private
* @param {OdooEvent} event
*/
_onNext: function (event) {
event.stopPropagation();
this._move('next');
},
/**
* @private
* @param {OdooEvent} event
@@ -305,6 +329,15 @@ var CalendarController = AbstractController.extend({
};
open_dialog(true);
},
/**
* @private
* @param {OdooEvent} event
*/
_onPrev: function () {
event.stopPropagation();
this._move('prev');
},
/**
* Handles saving data coming from quick create box
*
@@ -364,8 +397,7 @@ var CalendarController = AbstractController.extend({
this.$buttons.find('.active').removeClass('active');
this.$buttons.find('.o_calendar_button_' + this.mode).addClass('active');
}
var subtitle = (this.mode === 'week' ? _t('Week ') : '') + event.data.title;
this.set({title: this.displayName + ' (' + subtitle + ')'});
this.set({title: this.displayName + ' (' + event.data.title + ')'});
},
});
@@ -6,7 +6,6 @@ var Context = require('web.Context');
var core = require('web.core');
var fieldUtils = require('web.field_utils');
var session = require('web.session');
var time = require('web.time');
var _t = core._t;
@@ -400,12 +399,6 @@ return AbstractModel.extend({
weekNumbers: true,
weekNumberTitle: _t("W"),
allDayText: _t("All day"),
views: {
week: {
columnFormat: 'ddd ' + time.getLangDateFormat(),
titleFormat: time.getLangTimeFormat(),
}
},
monthNames: moment.months(),
monthNamesShort: moment.monthsShort(),
dayNames: moment.weekdays(),
@@ -2,6 +2,7 @@ odoo.define('web.CalendarRenderer', function (require) {
"use strict";
var AbstractRenderer = require('web.AbstractRenderer');
var config = require('web.config');
var core = require('web.core');
var Dialog = require('web.Dialog');
var field_utils = require('web.field_utils');
@@ -192,6 +193,9 @@ return AbstractRenderer.extend({
start: function () {
this._initSidebar();
this._initCalendar();
if (config.device.isMobile) {
this._bindSwipe();
}
return this._super();
},
/**
@@ -289,6 +293,27 @@ return AbstractRenderer.extend({
/**
* @private
* Bind handlers to enable swipe navigation
*
* @private
*/
_bindSwipe: function () {
var self = this;
var touchStartX;
var touchEndX;
this.$calendar.on('touchstart', function (event) {
touchStartX = event.originalEvent.touches[0].pageX;
});
this.$calendar.on('touchend', function (event) {
touchEndX = event.originalEvent.changedTouches[0].pageX;
if (touchStartX - touchEndX > 100) {
self.trigger_up('next');
} else if (touchStartX - touchEndX < -100) {
self.trigger_up('prev');
}
});
},
/**
* @param {any} event
* @returns {string} the html for the rendered event
*/
@@ -297,6 +322,7 @@ return AbstractRenderer.extend({
event: event,
fields: this.state.fields,
format: this._format.bind(this),
isMobile: config.device.isMobile,
read_only_mode: this.read_only_mode,
record: event.record,
user_context: session.user_context,
@@ -376,11 +402,9 @@ return AbstractRenderer.extend({
viewRender: function (view) {
// compute mode from view.name which is either 'month', 'agendaWeek' or 'agendaDay'
var mode = view.name === 'month' ? 'month' : (view.name === 'agendaWeek' ? 'week' : 'day');
// compute title: in week mode, display the week number
var title = mode === 'week' ? view.intervalStart.week() : view.title;
self.trigger_up('viewUpdated', {
mode: mode,
title: title,
title: view.title,
});
},
height: 'parent',
+28 -9
View File
@@ -66,9 +66,6 @@
}
.fc-view {
overflow-x: auto;
> table {
min-width: 600px;
}
}
}
@@ -202,12 +199,6 @@
}
}
@media only screen and (max-width: 768px) {
.o_calendar_container {
padding: 0;
}
}
.modal {
.o_attendee_head {
width: 32px;
@@ -220,3 +211,31 @@
display: none;
}
}
// Mobile rules
@media only screen and (max-width: 768px) {
.o_calendar_container {
padding: 0;
}
}
@media (max-width: @screen-xs-max) {
.o_control_panel {
.o_cp_left {
width: 100%;
.o_cp_buttons {
width: 100%;
.o_calendar_button_today {
float: right;
> .o_calendar_text {
margin-top: 3px;
}
}
}
}
}
.o_calendar_view {
.fc-day-header {
word-spacing: 1000px; // force line break in week mode
}
}
}
+13 -6
View File
@@ -20,9 +20,9 @@
<t t-foreach="widget.displayFields" t-as="name">
<div t-attf-class="o_field_#{name} o_field_type_#{fields[name].type}">
<t t-if="widget.displayFields[name].avatar_field">
<t t-esc="fields[name].string"/>:
<t t-if="!isMobile"><t t-esc="fields[name].string"/>:</t>
<div class="o_calendar_avatars pull-right">
<t t-foreach="widget.getAvatars(record, name, widget.displayFields[name].avatar_field).slice(0,3)" t-as="image"><t t-raw="image"/></t>
<t t-foreach="widget.getAvatars(record, name, widget.displayFields[name].avatar_field).slice(0,3)" t-as="image"><t t-raw="image"/></t>
<span t-if="record[name].length - 3 > 0">+<t t-esc="record[name].length - 3"/></span>
</div>
</t>
@@ -63,10 +63,17 @@
<t t-name="CalendarView.buttons">
<div class="o_calendar_buttons">
<button class="o_calendar_button_prev btn btn-sm btn-primary"><span class="fa fa-arrow-left"/></button>
<button class="o_calendar_button_today btn btn-sm btn-primary">Today</button>
<button class="o_calendar_button_next btn btn-sm btn-primary"><span class="fa fa-arrow-right"/></button>
<t t-if="isMobile">
<span class="fa-stack o_calendar_button_today">
<i class="fa fa-calendar-o fa-stack-2x"></i>
<strong class="o_calendar_text fa-stack-1x"><t t-esc="moment().date()"/></strong>
</span>
</t>
<t t-else="">
<button class="o_calendar_button_prev btn btn-sm btn-primary"><span class="fa fa-arrow-left"/></button>
<button class="o_calendar_button_today btn btn-sm btn-primary">Today</button>
<button class="o_calendar_button_next btn btn-sm btn-primary"><span class="fa fa-arrow-right"/></button>
</t>
<div class="btn-group btn-group-sm">
<button type="button" class="o_calendar_button_day btn btn-sm btn-default">Day</button>
<button type="button" class="o_calendar_button_week btn btn-sm btn-default">Week</button>
@@ -0,0 +1,62 @@
odoo.define('web.calendar_mobile_tests', function (require) {
"use strict";
var CalendarView = require('web.CalendarView');
var testUtils = require('web.test_utils');
var createView = testUtils.createView;
var initialDate = new Date(2016, 11, 12, 8, 0, 0);
initialDate = new Date(initialDate.getTime() - initialDate.getTimezoneOffset()*60*1000);
QUnit.module('Views', {
beforeEach: function () {
this.data = {
event: {
fields: {
id: {string: "ID", type: "integer"},
name: {string: "name", type: "char"},
start: {string: "start datetime", type: "datetime"},
stop: {string: "stop datetime", type: "datetime"},
},
records: [
{id: 1, name: "event 1", start: "2016-12-11 00:00:00", stop: "2016-12-11 00:00:00"},
],
check_access_rights: function () {
return $.when(true);
}
},
};
}
}, function () {
QUnit.module('CalendarView Mobile');
QUnit.test('simple calendar rendering in mobile', function (assert) {
assert.expect(3);
var calendar = createView({
View: CalendarView,
model: 'event',
data: this.data,
arch: '<calendar date_start="start" date_stop="stop">' +
'<field name="name"/>' +
'</calendar>',
viewOptions: {
initialDate: initialDate,
},
});
assert.notOk(calendar.$buttons.find('.o_calendar_button_prev').is(':visible'),
"prev button should be hidden");
assert.notOk(calendar.$buttons.find('.o_calendar_button_next').is(':visible'),
"next button should be hidden");
assert.ok(calendar.$buttons.find('.o_calendar_button_today').is(':visible'),
"today button should be visible");
calendar.destroy();
});
});
});
@@ -234,7 +234,7 @@ QUnit.module('Views', {
// displays month mode by default
assert.strictEqual(actionManager.controlPanel.$('.breadcrumb li').text(),
'Meetings Test (Week 51)', "should display the current week");
'Meetings Test (Dec 11 – 17, 2016)', "should display the current week");
// switch to day mode
actionManager.controlPanel.$('.o_calendar_button_day').click();
+1
View File
@@ -543,6 +543,7 @@
<script type="text/javascript" src="/web/static/lib/jquery.touchSwipe/jquery.touchSwipe.js"></script>
<script type="text/javascript" src="/web/static/tests/chrome/action_manager_mobile_tests.js"></script>
<script type="text/javascript" src="/web/static/tests/views/calendar_mobile_tests.js"></script>
<script type="text/javascript" src="/web/static/tests/views/kanban_mobile_tests.js"></script>
</t>