[REF] mail: activities list, ‘mark as done’ sliding panel

Refactor ‘mark as done’ popup JS in order to use a sliding panel layout instead.
Sliding panels are currently needed in kanban views, where activities are listed in a dropdown that can't handle popups because of CSS overflow and lack of space.

Since this new system check for default bootstrap data-toggle attributes, it could be easily used elsewhere changing the buttons's data-toggle and providing the necessary container for the “mark as done” form.
This commit is contained in:
sri-odoo
2018-09-18 13:44:00 +02:00
committed by XavierDo
parent 47496b9fe5
commit ed2773e381
5 changed files with 155 additions and 115 deletions
+75 -26
View File
@@ -271,39 +271,55 @@ var BasicActivity = AbstractField.extend({
ev.stopPropagation();
ev.preventDefault();
var self = this;
var $popoverElement = $(ev.currentTarget);
var activityID = $popoverElement.data('activity-id');
var previousActivityTypeID = $popoverElement.data('previous-activity-type-id');
var forceNextActivity = $popoverElement.data('force-next-activity');
if (!$popoverElement.data('bs.popover')) {
$popoverElement.popover({
var $markDoneBtn = $(ev.currentTarget);
var activityID = $markDoneBtn.data('activity-id');
var previousActivityTypeID = $markDoneBtn.data('previous-activity-type-id');
var forceNextActivity = $markDoneBtn.data('force-next-activity');
if ($markDoneBtn.data('toggle') == 'collapse') {
var $actLi = $markDoneBtn.parents('.o_log_activity');
var $panel = self.$('#o_activity_form_' + activityID);
if (!$panel.data('bs.collapse')) {
var $form = $(QWeb.render('mail.activity_feedback_form', { previous_activity_type_id: previousActivityTypeID, force_next: forceNextActivity}));
$panel.append($form);
self._onMarkActivityDoneActions($markDoneBtn, $form, activityID);
// Close and reset any other open panels
_.each($panel.siblings('.o_activity_form'), function (el) {
if ($(el).data('bs.collapse')) {
$(el).empty().collapse('dispose').removeClass('show');
}
});
// Scroll to selected activity
$markDoneBtn.parents('.o_activity_log_container').scrollTo($actLi.position().top, 100);
}
// Empty and reset panel on close
$panel.on('hidden.bs.collapse', function () {
if ($panel.data('bs.collapse')) {
$actLi.removeClass('o_activity_selected');
$panel.collapse('dispose');
$panel.empty();
}
});
this.$('.o_activity_selected').removeClass('o_activity_selected');
$actLi.toggleClass('o_activity_selected');
$panel.collapse('toggle');
} else if (!$markDoneBtn.data('bs.popover')) {
$markDoneBtn.popover({
template: $(Popover.Default.template).addClass('o_mail_activity_feedback')[0].outerHTML, // Ugly but cannot find another way
container: $popoverElement,
container: $markDoneBtn,
title : _t("Feedback"),
html: true,
trigger:'click',
placement: 'right', // FIXME: this should work, maybe a bug in the popper lib
content : function () {
var $popover = $(QWeb.render('mail.activity_feedback_form', { previous_activity_type_id: previousActivityTypeID, force_next: forceNextActivity}));
$popover.find('#activity_feedback').val(self._draftFeedback[activityID]);
$popover.on('click', '.o_activity_popover_done', function (ev) {
ev.stopPropagation();
self._markActivityDone({
activityID: activityID,
feedback: _.escape($popover.find('#activity_feedback').val()),
});
});
$popover.on('click', '.o_activity_popover_done_next', function (ev) {
ev.stopPropagation();
self._markActivityDoneAndScheduleNext({
activityID: activityID,
feedback: _.escape($popover.find('#activity_feedback').val()),
});
});
$popover.on('click', '.o_activity_popover_discard', function (ev) {
ev.stopPropagation();
$popoverElement.popover('hide');
});
self._onMarkActivityDoneActions($markDoneBtn, $popover, activityID);
return $popover;
},
}).on('shown.bs.popover', function () {
@@ -315,6 +331,39 @@ var BasicActivity = AbstractField.extend({
}).popover('show');
}
},
/**
* Bind all necessary actions to the 'mark as done' form
*
* @private
* @param {Object} $form
* @param {integer} activityID
*/
_onMarkActivityDoneActions: function ($btn, $form, activityID) {
var self = this;
$form.find('#activity_feedback').val(self._draftFeedback[activityID]);
$form.on('click', '.o_activity_popover_done', function (ev) {
ev.stopPropagation();
self._markActivityDone({
activityID: activityID,
feedback: _.escape($form.find('#activity_feedback').val()),
});
});
$form.on('click', '.o_activity_popover_done_next', function (ev) {
ev.stopPropagation();
self._markActivityDoneAndScheduleNext({
activityID: activityID,
feedback: _.escape($form.find('#activity_feedback').val()),
});
});
$form.on('click', '.o_activity_popover_discard', function (ev) {
ev.stopPropagation();
if ($btn.data('bs.popover')) {
$btn.popover('hide');
} else if ($btn.data('toggle') == 'collapse') {
self.$('#o_activity_form_' + activityID).collapse('hide');
}
});
},
/**
* @private
* @param {MouseEvent} ev
+30 -47
View File
@@ -123,13 +123,13 @@
}
.o_activity_color_planned {
color: theme-color('success');
color: darken(theme-color('success'), 10%);
}
.o_activity_color_overdue {
color: theme-color('danger');
color: darken(theme-color('danger'), 10%);
}
.o_activity_color_today {
color: theme-color('warning');
color: darken(theme-color('warning'), 10%);
}
}
@@ -160,62 +160,45 @@
}
}
.o_activity_link_kanban {
visibility: hidden;
@include media-breakpoint-down(sm) {
visibility: visible;
}
}
.o_activity_template_preview,.o_activity_template_send {
font-weight: bold;
color: $o-brand-primary;
cursor: pointer;
&:hover:not(.active) {
color: darken($o-brand-primary, 15%);
border-color: darken($o-brand-primary, 15%);
}
}
div.o_activity {
min-width: 290px;
padding: 0px 0px;
> :last-child {
background-color: $o-brand-lightsecondary;
line-height: 3em;
}
ul.o_activity_log {
.o_activity_log_container {
max-height: 300px;
overflow-y: auto;
}
ul.o_activity_log {
color: $body-color;
li {
width: 100%;
display: inline-block;
&.o_activity_label {
line-height: 2.5em;
padding: 0px 5px;
background-color: $o-brand-lightsecondary;
.o_activity_title_entry {
display: flex;
align-items: baseline;
}
&.o_log_activity {
padding: 8px 10px 8px 0px;
vertical-align: middle;
&:hover {
background-color: theme-color('light');
.o_activity_link_kanban {
visibility: visible;
}
}
.o_edit_button {
opacity: 0.5;
}
.o_activity_title {
width: 85%;
padding: 0px 5px 0px 10px;
white-space: nowrap;
text-overflow: ellipsis;
}
&:hover .o_edit_button{
opacity: 1;
}
.o_activity_link_kanban {
font-size: 1.5em;
@include o-hover-text-color($text-muted, theme-color('success'));
@include o-hover-opacity(0.5, 1);
}
&.o_activity_selected {
border-bottom: 0;
}
&.o_activity_form > div {
padding-top: 0.5em;
padding-bottom: 0.7em;
}
}
}
@@ -20,58 +20,65 @@
<span role="menuitem" t-if="_.isEmpty(records)" class="dropdown-item-text text-center o_no_activity">
No activities planned.
</span>
<div t-else="" aria-haspopup="true" role="menu">
<ul class="nav o_activity_log" role="menu">
<div t-else="" aria-haspopup="true" role="menu" class="o_activity_log_container dropdown-item bg-100 p-0">
<ul class="o_activity_log list-group list-group-flush mb-2" role="menu">
<t t-foreach="_.keys(records)" t-as="key">
<t t-set="logs" t-value="records[key]" />
<li role="menuitem" class="nav-item o_activity_label">
<strong t-attf-class="o_activity_color_#{key}">
<t t-esc="selection[key]" /> (<t t-esc="logs.length"/>)
</strong>
<t t-set="contextual_class" t-value="key == 'planned' ? 'success' : (key == 'today' ? 'warning' : 'danger') "/>
<li role="menuitem" t-attf-class="o_activity_label list-group-item list-group-item list-group-item-light d-flex justify-content-between align-items-center o_activity_color_{{key}} {{!key_first ? 'mt-2' : ''}}">
<strong><t t-esc="selection[key]"/></strong>
<span t-attf-class="badge badge-pill badge-{{contextual_class}} border-0 mr-0"><t t-esc="logs.length"/></span>
</li>
<t t-foreach="logs" t-as="log">
<t t-set="edit_class" t-value="'o_edit_activity'"/>
<t t-if="log.force_next">
<t t-set="edit_class" t-value=""/>
</t>
<li t-att-data-activity-id="log.id" t-attf-class="nav-item o_log_activity #{edit_class}" role="menuitem">
<div class="o_activity_title float-left">
<span t-attf-class="fa #{log.icon} fa-fw" role="img" aria-label="Log" title="Log"/>
<strong>
<t t-esc="log.title_action or log.activity_type_id[1]" />
</strong>
<div>
<span class="fa fa-clock-o fa-fw" role="img" aria-label="Deadline" title="Deadline"/>
<span t-att-title="log.date_deadline"><t t-esc="log.label_delay" /></span>
<div class="mt16" t-att-data-activity-id="log.id" t-att-data-force-next-activity="log.force_next" t-att-data-previous-activity-type-id="log.activity_type_id[0]">
<t t-if="log.mail_template_ids">
<t t-foreach="log.mail_template_ids" t-as="mail_template">
<div>
<i class="fa fa-envelope-o" aria-label="Mail" title="Mail" role="img"></i>
<span t-esc="mail_template.name"/>:
<span class="o_activity_template_preview" t-att-data-template-id="mail_template.id">Preview</span>
<span class="text-muted">or</span>
<span class="o_activity_template_send" t-att-data-template-id="mail_template.id">Send Now</span>
</div>
</t>
</t>
<li t-att-data-activity-id="log.id" t-attf-class="list-group-item o_log_activity d-flex #{edit_class} #{log_last ? 'border-bottom' : ''}" role="menuitem">
<div class="o_activity_title">
<div t-attf-class="o_activity_title_entry {{! log.force_next ? 'align-items-center' : 'mb-1'}}">
<span t-attf-class="fa #{log.icon ? log.icon : 'fa-bell' } fa-fw mr-2 text-center text-muted" role="img" aria-label="Log" title="Log"/>
<strong class="text-dark"><t t-esc="log.title_action or log.activity_type_id[1]"/></strong>
<button t-if="! log.force_next" class="btn btn-sm btn-link py-0 o_edit_button"><i class="fa fa-pencil"/></button>
</div>
<div class="o_activity_title_entry mt-1" t-if="log.state != 'today'">
<span class="fa fa-clock-o fa-fw mr-2 text-center text-muted" role="img" aria-label="Deadline" title="Deadline"/>
<small t-att-title="log.date_deadline"><t t-esc="log.label_delay" /></small>
</div>
<t t-if="log.mail_template_ids">
<div t-foreach="log.mail_template_ids" t-as="mail_template" class="o_activity_title_entry mt-2" t-att-data-activity-id="log.id" t-att-data-force-next-activity="log.force_next" t-att-data-previous-activity-type-id="log.activity_type_id[0]">
<i class="fa fa-envelope-o fa-fw mr-2 text-center text-muted" aria-label="Mail" title="Mail" role="img"></i>
<small>
<div class="mb-1" t-esc="mail_template.name + ':'"/>
<a class="o_activity_template_preview" t-att-data-template-id="mail_template.id" href="#"><b>Preview</b></a>
<small>or</small>
<a class="o_activity_template_send" t-att-data-template-id="mail_template.id" href="#"><b>Send Now</b></a>
</small>
</div>
<t t-if="log.user_id[0] != uid">
<span class="ml4 fa fa-user" role="img" aria-label="User" title="User"/>
<span><t t-esc="log.user_id[1]" /></span>
</t>
</t>
<div t-if="log.user_id[0] != uid and log.mail_template_ids" class="o_activity_title_entry">
<span class="fa fa-user fa-fw mr-2 text-center text-muted" role="img" aria-label="User" title="User"/>
<span><t t-esc="log.user_id[1]" /></span>
</div>
</div>
<div class="float-right">
<span class="o_mark_as_done o_activity_link o_activity_link_kanban fa fa-check-circle fa-2x mt4" t-att-data-force-next-activity="log.force_next" t-att-data-previous-activity-type-id="log.activity_type_id[0]" t-att-data-activity-id="log.id" title="Mark as done" role="img" aria-label="Mark as done"/>
<div class="flex-grow-1 text-right">
<a t-att-data-force-next-activity="log.force_next"
t-att-data-previous-activity-type-id="log.activity_type_id[0]"
t-att-data-activity-id="log.id"
t-attf-href="#o_mark_done_form{{log.id}}"
class="o_mark_as_done o_activity_link o_activity_link_kanban fa fa-check-circle"
data-toggle="collapse" title="Mark as done" role="img" aria-label="Mark as done"/>
</div>
</li>
<li t-attf-id="o_activity_form_{{log.id}}" class="o_activity_form list-group-item border-top-0 py-0 mb-2 collapse"></li>
</t>
</t>
</ul>
</div>
<div role="menuitem" class="o_schedule_activity text-center">
<a role="button" class="btn btn-link btn-block">
<div class="dropdown-divider mt-0"/>
<div role="menuitem" class="o_schedule_activity dropdown-header py-1 text-center">
<a role="button" class="btn btn-secondary mb-1">
<i class="fa fa-plus fa-fw"></i><strong>Schedule an activity</strong>
</a>
</div>
+4 -4
View File
@@ -308,13 +308,13 @@ QUnit.test('activity view: activity widget', function (assert) {
today.find('.o_closest_deadline').click();
assert.ok(dropdown.hasClass('show'), "dropdown should be displayed");
assert.ok(dropdown.find('.o_activity_color_today:contains(Today)').length, "Title should be today");
assert.ok(dropdown.find('.o_activity_title div div div:first span:contains(template8)').length,
assert.ok(dropdown.find('.o_activity_title_entry[data-activity-id="2"]:first div:contains(template8)').length,
"template8 should be available");
assert.ok(dropdown.find('.o_activity_title div div div:nth-child(2) span:contains(template9)').length,
assert.ok(dropdown.find('.o_activity_title_entry[data-activity-id="2"]:eq(1) div:contains(template9)').length,
"template9 should be available");
dropdown.find('.o_activity_title div div div:first .o_activity_template_preview').click();
dropdown.find('.o_activity_title div div div:first .o_activity_template_send').click();
dropdown.find('.o_activity_title_entry[data-activity-id="2"]:first .o_activity_template_preview').click();
dropdown.find('.o_activity_title_entry[data-activity-id="2"]:first .o_activity_template_send').click();
var overdue = activity.$('table tbody tr:first td:nth-child(3).overdue');
overdue.find('.o_closest_deadline').click();
dropdown = overdue.find('.dropdown-menu.o_activity');
+4 -3
View File
@@ -492,7 +492,7 @@ QUnit.test('kanban activity widget with no activity', function (assert) {
});
QUnit.test('kanban activity widget with an activity', function (assert) {
assert.expect(11);
assert.expect(12);
this.data.partner.records[0].activity_ids = [1];
this.data.partner.records[0].activity_state = 'today';
@@ -539,8 +539,9 @@ QUnit.test('kanban activity widget with an activity', function (assert) {
$record.find('.o_activity_btn').click();
assert.strictEqual(rpcCount, 2, 'a read should have been done to fetch the activity details');
assert.strictEqual($record.find('.o_activity_title').length, 1, "should have an activity scheduled");
var label_text = $record.find('.o_activity_label .o_activity_color_today').text();
assert.ok(label_text.indexOf('Today (1)') >= 0, "should display the correct label and count");
var label = $record.find('.o_activity_log .o_activity_color_today');
assert.strictEqual(label.find('strong').text(), "Today", "should display the correct label");
assert.strictEqual(label.find('.badge-warning').text(), "1", "should display the correct count");
// click on the activity button to close the dropdown
$record.find('.o_activity_btn').click();