[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:
@@ -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',
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user