[IMP] web: add optional fields parameter
This commit adds a new parameter on fields for list view and one2many. The parameter name is "optional" and its values are "hide" or "show". Columns of the list view which have the optional parameter set are listed in a dropdown that can be toggled from the last cell of the table header. The ones with optional set to "hide" are hidden by default. User choice is stored in local storage. If no saved parameter can be found in local storage then the default value from the view is used. Due to the overflow behavior of the responsive table, we were forced to wrap the table-responsive div in yet another div. We changed the o_list_view class on the table to o_list_table in order to reuse the more generic o_list_view class on the top-most div. CSS rules and selectors had to be updated according to this change. Task-1902765
This commit is contained in:
@@ -67,7 +67,7 @@ var SectionAndNoteListRenderer = ListRenderer.extend({
|
||||
_renderView: function () {
|
||||
var self = this;
|
||||
return this._super.apply(this, arguments).then(function () {
|
||||
self.$('> table').addClass('o_section_and_note_list_view');
|
||||
self.$('.o_list_table').addClass('o_section_and_note_list_view');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1041,7 +1041,7 @@ QUnit.module('account', {
|
||||
$('.ui-autocomplete .ui-menu-item a:contains(Search More):eq(1)').trigger('mouseenter').trigger('click');
|
||||
await testUtils.nextTick();
|
||||
assert.strictEqual($('.modal').length, 1, "should open a SelectCreateDialog");
|
||||
await testUtils.dom.click($('.modal table.o_list_view td:contains(Camptocamp)'));
|
||||
await testUtils.dom.click($('.modal table.o_list_table td:contains(Camptocamp)'));
|
||||
assert.strictEqual(widget.$('.o_input_dropdown input').val(), "Camptocamp", "the partner many2one should display Camptocamp");
|
||||
|
||||
widget = clientAction.widgets[2];
|
||||
|
||||
@@ -159,7 +159,7 @@ var ResumeLineRenderer = AbstractGroupedOne2ManyRenderer.extend({
|
||||
_render: function () {
|
||||
var self = this;
|
||||
return this._super().then(function () {
|
||||
self.$el.find('table').removeClass('table-striped o_list_view_ungrouped');
|
||||
self.$el.find('table').removeClass('table-striped o_list_table_ungrouped');
|
||||
self.$el.find('table').addClass('o_resume_table');
|
||||
});
|
||||
},
|
||||
|
||||
@@ -574,7 +574,7 @@ ListRenderer.include({
|
||||
* @returns {string} record dataPoint id
|
||||
*/
|
||||
_getRecordID: function (rowIndex) {
|
||||
var $tr = this.$('table.o_list_view > tbody tr').eq(rowIndex);
|
||||
var $tr = this.$('table.o_list_table > tbody tr').eq(rowIndex);
|
||||
return $tr.data('id');
|
||||
},
|
||||
/**
|
||||
@@ -996,6 +996,15 @@ ListRenderer.include({
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Unselect the row before adding the optional column to the listview
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_onToggleOptionalColumnDropdown: function (ev) {
|
||||
this.unselectRow().then(this._super.bind(this, ev));
|
||||
},
|
||||
/**
|
||||
* This method is called when we click on the 'Add a line' button in a groupby
|
||||
* list view.
|
||||
|
||||
@@ -32,7 +32,10 @@ var FIELD_CLASSES = {
|
||||
};
|
||||
|
||||
var ListRenderer = BasicRenderer.extend({
|
||||
className: 'o_list_view',
|
||||
events: {
|
||||
"click .o_optional_columns_dropdown .dropdown-item": "_onToggleOptionalColumn",
|
||||
"click .o_optional_columns_dropdown_toggle": "_onToggleOptionalColumnDropdown",
|
||||
'click tbody tr': '_onRowClicked',
|
||||
'click tbody .o_list_record_selector': '_onSelectRecord',
|
||||
'click thead th.o_column_sortable': '_onSortColumn',
|
||||
@@ -51,6 +54,7 @@ var ListRenderer = BasicRenderer.extend({
|
||||
*/
|
||||
init: function (parent, state, params) {
|
||||
this._super.apply(this, arguments);
|
||||
this.columnInvisibleFields = params.columnInvisibleFields;
|
||||
this.rowDecorations = _.chain(this.arch.attrs)
|
||||
.pick(function (value, key) {
|
||||
return DECORATIONS.indexOf(key) >= 0;
|
||||
@@ -63,7 +67,7 @@ var ListRenderer = BasicRenderer.extend({
|
||||
this.editable = params.editable;
|
||||
this.isGrouped = this.state.groupedBy.length > 0;
|
||||
this.groupbys = params.groupbys;
|
||||
this._processColumns(params.columnInvisibleFields || {});
|
||||
this._processColumns(this.columnInvisibleFields || {});
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -228,6 +232,21 @@ var ListRenderer = BasicRenderer.extend({
|
||||
var n = this.columns.length;
|
||||
return this.hasSelectors ? n + 1 : n;
|
||||
},
|
||||
/**
|
||||
* Returns the local storage key for stored enabled optional columns
|
||||
*
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
_getOptionalColumnsStorageKey: function () {
|
||||
var self = this;
|
||||
var fields = [];
|
||||
_.each(this.state.fieldsInfo[this.viewType], function (field, name) {
|
||||
fields.push(name + ":" + self.state.fields[name].type);
|
||||
});
|
||||
fields.sort();
|
||||
return "list_optional_fields," + this.state.model + "," + this.viewType + "," + fields.join(',');
|
||||
},
|
||||
/**
|
||||
* Removes the columns which should be invisible.
|
||||
*
|
||||
@@ -236,23 +255,45 @@ var ListRenderer = BasicRenderer.extend({
|
||||
_processColumns: function (columnInvisibleFields) {
|
||||
var self = this;
|
||||
this.handleField = null;
|
||||
this.columns = _.reject(this.arch.children, function (c) {
|
||||
if (c.tag === 'control' || c.tag === 'groupby') {
|
||||
return true;
|
||||
}
|
||||
var reject = c.attrs.modifiers.column_invisible;
|
||||
// If there is an evaluated domain for the field we override the node
|
||||
// attribute to have the evaluated modifier value.
|
||||
if (c.attrs.name in columnInvisibleFields) {
|
||||
reject = columnInvisibleFields[c.attrs.name];
|
||||
}
|
||||
if (!reject && c.attrs.widget === 'handle') {
|
||||
self.handleField = c.attrs.name;
|
||||
if (self.isGrouped) {
|
||||
return true;
|
||||
this.columns = [];
|
||||
this.optionalColumns = [];
|
||||
this.optionalColumnsEnabled = [];
|
||||
var localStorageKey = this._getOptionalColumnsStorageKey();
|
||||
var storedOptionalColumns = this.call('local_storage', 'getItem', localStorageKey);
|
||||
_.each(this.arch.children, function (c) {
|
||||
if (c.tag !== 'control' && c.tag !== 'groupby') {
|
||||
var reject = c.attrs.modifiers.column_invisible;
|
||||
// If there is an evaluated domain for the field we override the node
|
||||
// attribute to have the evaluated modifier value.
|
||||
if (c.attrs.name in columnInvisibleFields) {
|
||||
reject = columnInvisibleFields[c.attrs.name];
|
||||
}
|
||||
|
||||
if (!reject && c.attrs.widget === 'handle') {
|
||||
self.handleField = c.attrs.name;
|
||||
if (self.isGrouped) {
|
||||
reject = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!reject && c.attrs.optional) {
|
||||
self.optionalColumns.push(c);
|
||||
var enabled;
|
||||
if (storedOptionalColumns === undefined) {
|
||||
enabled = c.attrs.optional === 'show'
|
||||
} else {
|
||||
enabled = _.contains(storedOptionalColumns, c.attrs.name);
|
||||
}
|
||||
if (enabled) {
|
||||
self.optionalColumnsEnabled.push(c.attrs.name);
|
||||
}
|
||||
reject = !enabled;
|
||||
}
|
||||
|
||||
if (!reject) {
|
||||
self.columns.push(c);
|
||||
}
|
||||
}
|
||||
return reject;
|
||||
});
|
||||
},
|
||||
/**
|
||||
@@ -747,6 +788,46 @@ var ListRenderer = BasicRenderer.extend({
|
||||
_renderRows: function () {
|
||||
return this.state.data.map(this._renderRow.bind(this));
|
||||
},
|
||||
/**
|
||||
* Render a single <th> with dropdown menu to display optional columns of view.
|
||||
*
|
||||
* @private
|
||||
* @returns {jQueryElement} a <th> element
|
||||
*/
|
||||
_renderOptionalColumnsDropdown: function () {
|
||||
var self = this;
|
||||
var $optionalColumnsDropdown = $('<div>', {
|
||||
class: 'o_optional_columns text-center dropdown',
|
||||
});
|
||||
var $a = $("<a>", {
|
||||
class: "dropdown-toggle text-dark o-no-caret",
|
||||
href: "#",
|
||||
role: "button",
|
||||
'data-toggle': "dropdown",
|
||||
'aria-expanded': false,
|
||||
});
|
||||
$a.appendTo($optionalColumnsDropdown);
|
||||
var $dropdown = $("<div>", {
|
||||
class: 'dropdown-menu dropdown-menu-right o_optional_columns_dropdown',
|
||||
role: 'menu',
|
||||
});
|
||||
this.optionalColumns.forEach(function (col) {
|
||||
var txt = (col.attrs.string || self.state.fields[col.attrs.name].string) +
|
||||
(config.debug ? (' (' + col.attrs.name + ')') : '');
|
||||
var $checkbox = dom.renderCheckbox({
|
||||
text: txt,
|
||||
prop: {
|
||||
name: col.attrs.name,
|
||||
checked: _.contains(self.optionalColumnsEnabled, col.attrs.name),
|
||||
}
|
||||
})
|
||||
$dropdown.append($("<div>", {
|
||||
class: "dropdown-item",
|
||||
}).append($checkbox));
|
||||
});
|
||||
$dropdown.appendTo($optionalColumnsDropdown);
|
||||
return $optionalColumnsDropdown;
|
||||
},
|
||||
/**
|
||||
* A 'selector' is the small checkbox on the left of a record in a list
|
||||
* view. This is rendered as an input inside a div, so we can properly
|
||||
@@ -798,9 +879,9 @@ var ListRenderer = BasicRenderer.extend({
|
||||
this.hasHandle = orderedBy.length === 0 || orderedBy[0].name === this.handleField;
|
||||
this._computeAggregates();
|
||||
|
||||
var $table = $('<table>').addClass('o_list_view table table-sm table-hover table-striped');
|
||||
$table.toggleClass('o_list_view_grouped', this.isGrouped);
|
||||
$table.toggleClass('o_list_view_ungrouped', !this.isGrouped);
|
||||
var $table = $('<table>').addClass('o_list_table table table-sm table-hover table-striped');
|
||||
$table.toggleClass('o_list_table_grouped', this.isGrouped);
|
||||
$table.toggleClass('o_list_table_ungrouped', !this.isGrouped);
|
||||
var defs = [];
|
||||
this.defs = defs;
|
||||
if (this.isGrouped) {
|
||||
@@ -819,7 +900,16 @@ var ListRenderer = BasicRenderer.extend({
|
||||
// destroy the previously instantiated pagers, if any
|
||||
_.invoke(oldPagers, 'destroy');
|
||||
|
||||
self.$el.addClass('table-responsive').html($table);
|
||||
self.$el.html($('<div>', {
|
||||
class: 'table-responsive',
|
||||
html: $table
|
||||
}));
|
||||
|
||||
if (self.optionalColumns.length) {
|
||||
self.$el.addClass('o_list_optional_columns')
|
||||
self.$('table').append($('<i class="o_optional_columns_dropdown_toggle fa fa-ellipsis-v"/>'));
|
||||
self.$el.append(self._renderOptionalColumnsDropdown());
|
||||
}
|
||||
|
||||
if (self.selection.length) {
|
||||
var $checked_rows = self.$('tr').filter(function (index, el) {
|
||||
@@ -914,6 +1004,45 @@ var ListRenderer = BasicRenderer.extend({
|
||||
ev.stopPropagation();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* When the user clicks on the checkbox in optional fields dropdown the
|
||||
* column is added to listview and displayed
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onToggleOptionalColumn: function (ev) {
|
||||
var self = this;
|
||||
ev.stopPropagation();
|
||||
var input = ev.currentTarget.querySelector('input');
|
||||
var fieldIndex = this.optionalColumnsEnabled.indexOf(input.name);
|
||||
if (fieldIndex >= 0) {
|
||||
this.optionalColumnsEnabled.splice(fieldIndex, 1);
|
||||
} else {
|
||||
this.optionalColumnsEnabled.push(input.name);
|
||||
}
|
||||
this.call('local_storage', 'setItem', this._getOptionalColumnsStorageKey(), this.optionalColumnsEnabled);
|
||||
this._processColumns(this.columnInvisibleFields || {});
|
||||
this._renderView().then(function() {
|
||||
self._onToggleOptionalColumnDropdown(ev);
|
||||
})
|
||||
},
|
||||
/**
|
||||
* When the user clicks on the three dots (ellipsis), toggle the optional
|
||||
* fields dropdown.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_onToggleOptionalColumnDropdown: function (ev) {
|
||||
// The dropdown toggle is inside the overflow hidden container because
|
||||
// the ellipsis is always in the last column, but we want the actual
|
||||
// dropdown to be outside of the overflow hidden container since it
|
||||
// could easily have a higher height than the table. However, separating
|
||||
// the toggle and the dropdown itself is not supported by popper.js by
|
||||
// default, which is why we need to toggle the dropdown manually.
|
||||
ev.stopPropagation();
|
||||
this.$('.o_optional_columns .dropdown-toggle').dropdown('toggle');
|
||||
},
|
||||
/**
|
||||
* Manages the keyboard events on the list. If the list is not editable, when the user navigates to
|
||||
* a cell using the keyboard, if he presses enter, enter the model represented by the line
|
||||
|
||||
@@ -1,233 +1,266 @@
|
||||
|
||||
.o_list_view {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
background-color: $o-view-background-color;
|
||||
margin-bottom: 0px;
|
||||
|
||||
// Don't understand why but without those, the list view appears broken
|
||||
// in both form views and standalone list views.
|
||||
border-collapse: initial;
|
||||
border-spacing: 0;
|
||||
|
||||
thead {
|
||||
color: $o-main-text-color;
|
||||
border-bottom: 1px solid #cacaca;
|
||||
> tr > th:not(.o_list_record_selector) {
|
||||
border-left: 1px solid #dfdfdf;
|
||||
@include o-text-overflow(table-cell);
|
||||
&:not(.o_column_sortable):hover {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
tbody > tr > td:not(.o_list_record_selector) {
|
||||
@include o-text-overflow(table-cell);
|
||||
|
||||
&.o_list_number {
|
||||
text-align: right;
|
||||
}
|
||||
&.o_list_text {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
|
||||
tfoot {
|
||||
cursor: default;
|
||||
color: $o-list-footer-color;
|
||||
background-color: $o-list-footer-bg-color;
|
||||
font-weight: $o-list-footer-font-weight;
|
||||
border-top: 2px solid #cacaca;
|
||||
border-bottom: 1px solid #cacaca;
|
||||
}
|
||||
|
||||
.o_column_sortable {
|
||||
.o_list_table {
|
||||
position: relative;
|
||||
user-select: none; // Prevent unwanted selection while sorting
|
||||
|
||||
&::after {
|
||||
font-family: FontAwesome;
|
||||
content: "\f0d7";
|
||||
opacity: 0;
|
||||
}
|
||||
&:not(:empty)::after {
|
||||
margin-left: 6px;
|
||||
}
|
||||
&.o-sort-up {
|
||||
cursor: n-resize;
|
||||
&::after {
|
||||
content: "\f0d7";
|
||||
}
|
||||
}
|
||||
&.o-sort-down {
|
||||
cursor: s-resize;
|
||||
&::after {
|
||||
content: "\f0d8";
|
||||
}
|
||||
}
|
||||
&:hover::after {
|
||||
opacity: 0.3;
|
||||
}
|
||||
&.o-sort-up, &.o-sort-down {
|
||||
color: $headings-color;
|
||||
&::after {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_list_record_selector, .o_list_record_remove, .o_handle_cell {
|
||||
width: 1px; // to prevent the column to expand
|
||||
}
|
||||
|
||||
.o_list_record_remove button {
|
||||
padding: 0px;
|
||||
background: none;
|
||||
border-style: none;
|
||||
display: table-cell;
|
||||
cursor: pointer;
|
||||
}
|
||||
background-color: $o-view-background-color;
|
||||
margin-bottom: 0px;
|
||||
|
||||
// Contextual classes
|
||||
@each $color, $value in $theme-colors {
|
||||
$safe: saturate(theme-color-level($color, 2), 10%);
|
||||
@include text-emphasis-variant(".text-#{$color}", $safe);
|
||||
}
|
||||
// Don't understand why but without those, the list view appears broken
|
||||
// in both form views and standalone list views.
|
||||
border-collapse: initial;
|
||||
border-spacing: 0;
|
||||
|
||||
// Grouped list views
|
||||
tbody > tr.o_group_header {
|
||||
> th, > td {
|
||||
vertical-align: middle;
|
||||
padding-top: ($table-cell-padding-sm*2);
|
||||
padding-bottom: ($table-cell-padding-sm*2);
|
||||
}
|
||||
.o_group_name {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.o_group_buttons {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
|
||||
> button {
|
||||
@include o-hover-text-color($o-brand-primary, darken($o-brand-primary, 20%));
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
thead {
|
||||
color: $o-main-text-color;
|
||||
border-bottom: 1px solid #cacaca;
|
||||
> tr > th:not(.o_list_record_selector) {
|
||||
border-left: 1px solid #dfdfdf;
|
||||
@include o-text-overflow(table-cell);
|
||||
&:not(.o_column_sortable):hover {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
.o_group_pager {
|
||||
tbody > tr > td:not(.o_list_record_selector) {
|
||||
@include o-text-overflow(table-cell);
|
||||
|
||||
&.o_list_number {
|
||||
text-align: right;
|
||||
}
|
||||
&.o_list_text {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
|
||||
tfoot {
|
||||
cursor: default;
|
||||
color: $o-list-footer-color;
|
||||
background-color: $o-list-footer-bg-color;
|
||||
font-weight: $o-list-footer-font-weight;
|
||||
border-top: 2px solid #cacaca;
|
||||
border-bottom: 1px solid #cacaca;
|
||||
}
|
||||
|
||||
.o_column_sortable {
|
||||
position: relative;
|
||||
user-select: none; // Prevent unwanted selection while sorting
|
||||
|
||||
.o_pager {
|
||||
@include o-position-absolute(0, 16px);
|
||||
cursor: text;
|
||||
user-select: none;
|
||||
padding-top: 3px !important;
|
||||
padding-bottom: 0 !important;
|
||||
&::after {
|
||||
font-family: FontAwesome;
|
||||
content: "\f0d7";
|
||||
opacity: 0;
|
||||
}
|
||||
.o_pager_previous, .o_pager_next {
|
||||
max-height: 30px;
|
||||
background-color: $o-list-group-header-color;
|
||||
&:hover {
|
||||
background-color: darken($o-list-group-header-color, 10%);
|
||||
&:not(:empty)::after {
|
||||
margin-left: 6px;
|
||||
}
|
||||
&.o-sort-up {
|
||||
cursor: n-resize;
|
||||
&::after {
|
||||
content: "\f0d7";
|
||||
}
|
||||
}
|
||||
&.o-sort-down {
|
||||
cursor: s-resize;
|
||||
&::after {
|
||||
content: "\f0d8";
|
||||
}
|
||||
}
|
||||
&:hover::after {
|
||||
opacity: 0.3;
|
||||
}
|
||||
&.o-sort-up, &.o-sort-down {
|
||||
color: $headings-color;
|
||||
&::after {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tbody + tbody {
|
||||
border-top: none; // Override bootstrap for grouped list views
|
||||
}
|
||||
|
||||
&, &.table-striped {
|
||||
tr:focus-within {
|
||||
background-color: lighten($o-form-lightsecondary, 10%);
|
||||
}
|
||||
}
|
||||
thead th:focus-within, tbody td:focus-within {
|
||||
background-color: $o-form-lightsecondary;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.o_data_cell.o_text_overflow,
|
||||
.o_data_cell.o_invisible_modifier {
|
||||
// Override display: none to keep table cells aligned. Note: cannot use
|
||||
// 'initial' as it will force them to 'inline', not 'table-cell'.
|
||||
display: table-cell!important;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Edition
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
&.o_editable_list {
|
||||
table-layout: fixed;
|
||||
|
||||
.o_list_record_selector {
|
||||
width: 40px;
|
||||
}
|
||||
.o_list_record_remove_header, .o_handle_cell {
|
||||
width: 32px;
|
||||
.o_list_record_selector, .o_list_record_remove, .o_handle_cell {
|
||||
width: 1px; // to prevent the column to expand
|
||||
}
|
||||
|
||||
// checkboxes in editable lists should be clickable and activate the row
|
||||
.o_data_row:not(.o_selected_row) .o_data_cell {
|
||||
.custom-checkbox:not(.o_readonly_modifier) {
|
||||
pointer-events: none;
|
||||
.o_list_record_remove button {
|
||||
padding: 0px;
|
||||
background: none;
|
||||
border-style: none;
|
||||
display: table-cell;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
// Contextual classes
|
||||
@each $color, $value in $theme-colors {
|
||||
$safe: saturate(theme-color-level($color, 2), 10%);
|
||||
@include text-emphasis-variant(".text-#{$color}", $safe);
|
||||
}
|
||||
|
||||
// Grouped list views
|
||||
tbody > tr.o_group_header {
|
||||
> th, > td {
|
||||
vertical-align: middle;
|
||||
padding-top: ($table-cell-padding-sm*2);
|
||||
padding-bottom: ($table-cell-padding-sm*2);
|
||||
}
|
||||
}
|
||||
|
||||
.o_data_row.o_selected_row > .o_data_cell:not(.o_readonly_modifier) {
|
||||
position: relative; // for o_field_translate
|
||||
background-color: white;
|
||||
|
||||
.o_input {
|
||||
border: none;
|
||||
padding: 0;
|
||||
.o_group_name {
|
||||
white-space: nowrap;
|
||||
}
|
||||
> .o_field_text {
|
||||
vertical-align: top;
|
||||
}
|
||||
> .o_field_widget {
|
||||
width: 100%;
|
||||
> .o_external_button {
|
||||
padding: 0;
|
||||
.o_group_buttons {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
|
||||
> button {
|
||||
@include o-hover-text-color($o-brand-primary, darken($o-brand-primary, 20%));
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
background-color: inherit;
|
||||
margin-left: 5px;
|
||||
font-size: 12px;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
> .o_field_monetary input {
|
||||
width: 0; // override width: 100px and let flex makes this input grow
|
||||
margin: 0 4px;
|
||||
}
|
||||
> .o_field_many2manytags > .o_field_many2one {
|
||||
// reduce basis to prevent many2one input from going to the next line to often
|
||||
flex-basis: 40px;
|
||||
}
|
||||
.o_input_dropdown, .o_datepicker {
|
||||
> .o_input {
|
||||
padding-right: 15px; // to prevent caret overlapping
|
||||
.o_group_pager {
|
||||
position: relative;
|
||||
|
||||
.o_pager {
|
||||
@include o-position-absolute(0, 16px);
|
||||
cursor: text;
|
||||
user-select: none;
|
||||
padding-top: 3px !important;
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
> .o_dropdown_button, .o_datepicker_button {
|
||||
@include o-position-absolute(0, 0);
|
||||
.o_pager_previous, .o_pager_next {
|
||||
max-height: 30px;
|
||||
background-color: $o-list-group-header-color;
|
||||
&:hover {
|
||||
background-color: darken($o-list-group-header-color, 10%);
|
||||
}
|
||||
}
|
||||
}
|
||||
> .o_field_translate {
|
||||
@include o-position-absolute(0, 5px);
|
||||
width: 16px; // to have some space on the right
|
||||
}
|
||||
tbody + tbody {
|
||||
border-top: none; // Override bootstrap for grouped list views
|
||||
}
|
||||
|
||||
&, &.table-striped {
|
||||
tr:focus-within {
|
||||
background-color: lighten($o-form-lightsecondary, 10%);
|
||||
}
|
||||
> .o_row_handle {
|
||||
visibility: hidden; // hide sequence when editing
|
||||
}
|
||||
thead th:focus-within, tbody td:focus-within {
|
||||
background-color: $o-form-lightsecondary;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.o_data_cell.o_text_overflow,
|
||||
.o_data_cell.o_invisible_modifier {
|
||||
// Override display: none to keep table cells aligned. Note: cannot use
|
||||
// 'initial' as it will force them to 'inline', not 'table-cell'.
|
||||
display: table-cell!important;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Edition
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
&.o_editable_list {
|
||||
table-layout: fixed;
|
||||
|
||||
.o_list_record_selector {
|
||||
width: 40px;
|
||||
}
|
||||
.o_list_record_remove_header, .o_handle_cell {
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
// checkboxes in editable lists should be clickable and activate the row
|
||||
.o_data_row:not(.o_selected_row) .o_data_cell {
|
||||
.custom-checkbox:not(.o_readonly_modifier) {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.o_data_row.o_selected_row > .o_data_cell:not(.o_readonly_modifier) {
|
||||
position: relative; // for o_field_translate
|
||||
background-color: white;
|
||||
|
||||
.o_input {
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
> .o_field_text {
|
||||
vertical-align: top;
|
||||
}
|
||||
> .o_field_widget {
|
||||
width: 100%;
|
||||
> .o_external_button {
|
||||
padding: 0;
|
||||
border: none;
|
||||
background-color: inherit;
|
||||
margin-left: 5px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
> .o_field_monetary input {
|
||||
width: 0; // override width: 100px and let flex makes this input grow
|
||||
margin: 0 4px;
|
||||
}
|
||||
> .o_field_many2manytags > .o_field_many2one {
|
||||
// reduce basis to prevent many2one input from going to the next line to often
|
||||
flex-basis: 40px;
|
||||
}
|
||||
.o_input_dropdown, .o_datepicker {
|
||||
> .o_input {
|
||||
padding-right: 15px; // to prevent caret overlapping
|
||||
}
|
||||
> .o_dropdown_button, .o_datepicker_button {
|
||||
@include o-position-absolute(0, 0);
|
||||
}
|
||||
}
|
||||
> .o_field_translate {
|
||||
@include o-position-absolute(0, 5px);
|
||||
width: 16px; // to have some space on the right
|
||||
}
|
||||
> .o_row_handle {
|
||||
visibility: hidden; // hide sequence when editing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_field_x2many_list_row_add, .o_group_field_row_add {
|
||||
a:focus, a:active {
|
||||
color: $link-hover-color;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_field_x2many_list_row_add, .o_group_field_row_add {
|
||||
a:focus, a:active {
|
||||
color: $link-hover-color;
|
||||
outline: none;
|
||||
// Optional fields
|
||||
&.o_list_optional_columns {
|
||||
th:last-child {
|
||||
padding-right: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.o_optional_columns_dropdown_toggle,.o_optional_columns {
|
||||
@include o-position-absolute($top: 0, $right: 0);
|
||||
}
|
||||
|
||||
.o_optional_columns_dropdown_toggle {
|
||||
width: 10px;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.o_optional_columns {
|
||||
.o_optional_columns_dropdown {
|
||||
margin-top: 30px;
|
||||
user-select: none;
|
||||
.dropdown-item {
|
||||
label {
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2889,7 +2889,7 @@ QUnit.module('ActionManager', {
|
||||
});
|
||||
await actionManager.doAction(3);
|
||||
|
||||
assert.doesNotHaveClass(actionManager.$('.o_list_view'), 'o_list_view_grouped',
|
||||
assert.doesNotHaveClass(actionManager.$('.o_list_table'), 'o_list_table_grouped',
|
||||
"list view is not grouped");
|
||||
|
||||
// open group by dropdown
|
||||
@@ -2898,7 +2898,7 @@ QUnit.module('ActionManager', {
|
||||
// click on first link
|
||||
await testUtils.dom.click($('.o_control_panel .o_group_by_menu a:first'));
|
||||
|
||||
assert.hasClass(actionManager.$('.o_list_view'),'o_list_view_grouped',
|
||||
assert.hasClass(actionManager.$('.o_list_table'),'o_list_table_grouped',
|
||||
'list view is now grouped');
|
||||
|
||||
actionManager.destroy();
|
||||
@@ -3110,7 +3110,7 @@ QUnit.module('ActionManager', {
|
||||
});
|
||||
await actionManager.doAction(3);
|
||||
|
||||
assert.containsOnce(actionManager, '.o_list_view_grouped',
|
||||
assert.containsOnce(actionManager, '.o_list_table_grouped',
|
||||
"should be grouped");
|
||||
assert.containsN(actionManager, '.o_group_header', 2,
|
||||
"should be grouped by 'bar' (two groups) at first load");
|
||||
@@ -3125,7 +3125,7 @@ QUnit.module('ActionManager', {
|
||||
// remove the groupby in the searchview
|
||||
await testUtils.dom.click($('.o_control_panel .o_searchview .o_facet_remove'));
|
||||
|
||||
assert.containsOnce(actionManager, '.o_list_view_grouped',
|
||||
assert.containsOnce(actionManager, '.o_list_table_grouped',
|
||||
"should still be grouped");
|
||||
assert.containsN(actionManager, '.o_group_header', 2,
|
||||
"should be grouped by 'bar' (two groups) at reload");
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
odoo.define('web.relational_fields_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
var AbstractStorageService = require('web.AbstractStorageService');
|
||||
var FormView = require('web.FormView');
|
||||
var ListView = require('web.ListView');
|
||||
var RamStorage = require('web.RamStorage');
|
||||
var relationalFields = require('web.relational_fields');
|
||||
var testUtils = require('web.test_utils');
|
||||
|
||||
@@ -2888,6 +2890,81 @@ QUnit.module('relational_fields', {
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('one2many field in edit mode with optional fields and trash icon', async function (assert) {
|
||||
assert.expect(13);
|
||||
|
||||
var RamStorageService = AbstractStorageService.extend({
|
||||
storage: new RamStorage(),
|
||||
});
|
||||
|
||||
this.data.partner.records[0].p = [2];
|
||||
var form = await createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<form string="Partners">' +
|
||||
'<field name="p"/>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
archs: {
|
||||
'partner,false,list': '<tree editable="top">' +
|
||||
'<field name="foo" optional="show"/>' +
|
||||
'<field name="bar" optional="hide"/>' +
|
||||
'</tree>',
|
||||
},
|
||||
services: {
|
||||
local_storage: RamStorageService,
|
||||
},
|
||||
});
|
||||
|
||||
// should have 2 columns 1 for foo and 1 for advanced dropdown
|
||||
assert.containsN(form.$('.o_field_one2many'), 'th', 1,
|
||||
"should be 1 th in the one2many in readonly mode");
|
||||
assert.containsOnce(form.$('.o_field_one2many table'), '.o_optional_columns_dropdown_toggle',
|
||||
"should have the optional columns dropdown toggle inside the table");
|
||||
await testUtils.form.clickEdit(form);
|
||||
// should have 2 columns 1 for foo and 1 for trash icon, dropdown is displayed
|
||||
// on trash icon cell, no separate cell created for trash icon and advanced field dropdown
|
||||
assert.containsN(form.$('.o_field_one2many'), 'th', 2,
|
||||
"should be 2 th in the one2many edit mode");
|
||||
assert.containsN(form.$('.o_field_one2many'), '.o_data_row:first > td', 2,
|
||||
"should be 2 cells in the one2many in edit mode");
|
||||
|
||||
await testUtils.dom.click(form.$('.o_field_one2many table .o_optional_columns_dropdown_toggle'));
|
||||
assert.containsN(form.$('.o_field_one2many'), 'div.o_optional_columns div.dropdown-item:visible', 2,
|
||||
"dropdown have 2 advanced field foo with checked and bar with unchecked");
|
||||
await testUtils.dom.click(form.$('div.o_optional_columns div.dropdown-item:eq(1) input'));
|
||||
assert.containsN(form.$('.o_field_one2many'), 'th', 3,
|
||||
"should be 3 th in the one2many after enabling bar column from advanced dropdown");
|
||||
|
||||
await testUtils.dom.click(form.$('div.o_optional_columns div.dropdown-item:first input'));
|
||||
assert.containsN(form.$('.o_field_one2many'), 'th', 2,
|
||||
"should be 2 th in the one2many after disabling foo column from advanced dropdown");
|
||||
|
||||
assert.containsN(form.$('.o_field_one2many'), 'div.o_optional_columns div.dropdown-item:visible', 2,
|
||||
"dropdown is still open");
|
||||
await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
|
||||
assert.containsN(form.$('.o_field_one2many'), 'div.o_optional_columns div.dropdown-item:visible', 0,
|
||||
"dropdown is closed");
|
||||
var $selectedRow = form.$('.o_field_one2many tr.o_selected_row');
|
||||
assert.strictEqual($selectedRow.length, 1, "should have selected row i.e. edition mode");
|
||||
|
||||
await testUtils.dom.click(form.$('.o_field_one2many table .o_optional_columns_dropdown_toggle'));
|
||||
await testUtils.dom.click(form.$('div.o_optional_columns div.dropdown-item:first input'));
|
||||
$selectedRow = form.$('.o_field_one2many tr.o_selected_row');
|
||||
assert.strictEqual($selectedRow.length, 0,
|
||||
"current edition mode discarded when selecting advanced field");
|
||||
assert.containsN(form.$('.o_field_one2many'), 'th', 3,
|
||||
"should be 3 th in the one2many after re-enabling foo column from advanced dropdown");
|
||||
|
||||
// check after form reload advanced column hidden or shown are still preserved
|
||||
await form.reload();
|
||||
assert.containsN(form.$('.o_field_one2many .o_list_view'), 'th', 3,
|
||||
"should still have 3 th in the one2many after reloading whole form view");
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.module('TabNavigation');
|
||||
QUnit.test('when Navigating to a many2one with tabs, it receives the focus and adds a new line', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
@@ -245,18 +245,18 @@ QUnit.module('Views', {
|
||||
});
|
||||
assert.containsOnce(form, 'div.o_field_one2many:eq(0):not(.o_readonly_modifier)', "first one2many widget should not be readonly");
|
||||
assert.hasClass(form.$('div.o_field_one2many:eq(1)'),'o_readonly_modifier', "second one2many widget should be readonly");
|
||||
await testUtils.dom.click(form.$('div.tab-content table.o_list_view:eq(0) tr.o_data_row td.o_data_cell:eq(0)'));
|
||||
assert.strictEqual(form.$('div.tab-content table.o_list_view tr.o_selected_row input[name="foo"]').val(), "yop",
|
||||
await testUtils.dom.click(form.$('div.tab-content table.o_list_table:eq(0) tr.o_data_row td.o_data_cell:eq(0)'));
|
||||
assert.strictEqual(form.$('div.tab-content table.o_list_table tr.o_selected_row input[name="foo"]').val(), "yop",
|
||||
"first line in one2many of first tab contains yop");
|
||||
assert.strictEqual(form.$('div.tab-content table.o_list_view:eq(1) tr.o_data_row td.o_data_cell:eq(0)').text(),
|
||||
assert.strictEqual(form.$('div.tab-content table.o_list_table:eq(1) tr.o_data_row td.o_data_cell:eq(0)').text(),
|
||||
"yop", "first line in one2many of second tab contains yop");
|
||||
await testUtils.fields.editInput(form.$('div.tab-content table.o_list_view tr.o_selected_row input[name="foo"]'), "hello");
|
||||
assert.strictEqual(form.$('div.tab-content table.o_list_view:eq(1) tr.o_data_row td.o_data_cell:eq(0)').text(), "hello",
|
||||
await testUtils.fields.editInput(form.$('div.tab-content table.o_list_table tr.o_selected_row input[name="foo"]'), "hello");
|
||||
assert.strictEqual(form.$('div.tab-content table.o_list_table:eq(1) tr.o_data_row td.o_data_cell:eq(0)').text(), "hello",
|
||||
"first line in one2many of second tab contains hello");
|
||||
await testUtils.dom.click(form.$('div.tab-content table.o_list_view:eq(0) a:contains(Add a line)'));
|
||||
assert.strictEqual(form.$('div.tab-content table.o_list_view tr.o_selected_row input[name="foo"]').val(), "My little Foo Value",
|
||||
await testUtils.dom.click(form.$('div.tab-content table.o_list_table:eq(0) a:contains(Add a line)'));
|
||||
assert.strictEqual(form.$('div.tab-content table.o_list_table tr.o_selected_row input[name="foo"]').val(), "My little Foo Value",
|
||||
"second line in one2many of first tab contains 'My little Foo Value'");
|
||||
assert.strictEqual(form.$('div.tab-content table.o_list_view:eq(1) tr.o_data_row:eq(1) td.o_data_cell:eq(0)').text(),
|
||||
assert.strictEqual(form.$('div.tab-content table.o_list_table:eq(1) tr.o_data_row:eq(1) td.o_data_cell:eq(0)').text(),
|
||||
"My little Foo Value", "first line in one2many of second tab contains hello");
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
odoo.define('web.list_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
var AbstractStorageService = require('web.AbstractStorageService');
|
||||
var BasicModel = require('web.BasicModel');
|
||||
var config = require('web.config');
|
||||
var core = require('web.core');
|
||||
@@ -9,6 +10,7 @@ var FormView = require('web.FormView');
|
||||
var ListView = require('web.ListView');
|
||||
var mixins = require('web.mixins');
|
||||
var NotificationService = require('web.NotificationService');
|
||||
var RamStorage = require('web.RamStorage');
|
||||
var testUtils = require('web.test_utils');
|
||||
var widgetRegistry = require('web.widget_registry');
|
||||
var Widget = require('web.Widget');
|
||||
@@ -2295,7 +2297,7 @@ QUnit.module('Views', {
|
||||
});
|
||||
|
||||
await testUtils.dom.click(list.$buttons.find('.o_list_button_add'));
|
||||
await testUtils.dom.click(list.$('div.table-responsive'));
|
||||
await testUtils.dom.click(list.$('.o_list_view'));
|
||||
|
||||
assert.strictEqual(createCount, 1, "should have created a record");
|
||||
|
||||
@@ -6134,6 +6136,302 @@ QUnit.module('Views', {
|
||||
list.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('list view with optional fields rendering', async function (assert) {
|
||||
assert.expect(9);
|
||||
|
||||
var RamStorageService = AbstractStorageService.extend({
|
||||
storage: new RamStorage(),
|
||||
});
|
||||
|
||||
var list = await createView({
|
||||
View: ListView,
|
||||
model: 'foo',
|
||||
data: this.data,
|
||||
arch: '<tree>' +
|
||||
'<field name="foo"/>' +
|
||||
'<field name="m2o" optional="hide"/>' +
|
||||
'<field name="amount"/>' +
|
||||
'<field name="reference" optional="hide"/>' +
|
||||
'</tree>',
|
||||
services: {
|
||||
local_storage: RamStorageService,
|
||||
},
|
||||
});
|
||||
|
||||
assert.containsN(list, 'th', 3,
|
||||
"should have 3 th, 1 for selector, 2 for columns");
|
||||
|
||||
assert.containsOnce(list.$('table'), '.o_optional_columns_dropdown_toggle',
|
||||
"should have the optional columns dropdown toggle inside the table");
|
||||
|
||||
// optional fields
|
||||
await testUtils.dom.click(list.$('table .o_optional_columns_dropdown_toggle'));
|
||||
assert.containsN(list, 'div.o_optional_columns div.dropdown-item', 2,
|
||||
"dropdown have 2 optional field foo with checked and bar with unchecked");
|
||||
|
||||
// enable optional field
|
||||
await testUtils.dom.click(list.$('div.o_optional_columns div.dropdown-item:first input'));
|
||||
// 5 th (1 for checkbox, 4 for columns)
|
||||
assert.containsN(list, 'th', 4, "should have 4 th");
|
||||
assert.ok(list.$('th:contains(M2O field)').is(':visible'),
|
||||
"should have a visible m2o field"); //m2o field
|
||||
|
||||
// disable optional field
|
||||
await testUtils.dom.click(list.$('table .o_optional_columns_dropdown_toggle'));
|
||||
assert.strictEqual(list.$('div.o_optional_columns div.dropdown-item:first input:checked')[0],
|
||||
list.$('div.o_optional_columns div.dropdown-item [name="m2o"]')[0],
|
||||
"m2o advanced field check box should be checked in dropdown");
|
||||
|
||||
await testUtils.dom.click(list.$('div.o_optional_columns div.dropdown-item:first input'));
|
||||
// 3 th (1 for checkbox, 2 for columns)
|
||||
assert.containsN(list, 'th', 3, "should have 3 th");
|
||||
assert.notOk(list.$('th:contains(M2O field)').is(':visible'),
|
||||
"should not have a visible m2o field"); //m2o field not displayed
|
||||
|
||||
await testUtils.dom.click(list.$('table .o_optional_columns_dropdown_toggle'));
|
||||
assert.notOk(list.$('div.o_optional_columns div.dropdown-item [name="m2o"]').is(":checked"));
|
||||
|
||||
list.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('optinal fields do not disappear even after listview reload', async function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
var RamStorageService = AbstractStorageService.extend({
|
||||
storage: new RamStorage(),
|
||||
});
|
||||
|
||||
var list = await createView({
|
||||
View: ListView,
|
||||
model: 'foo',
|
||||
data: this.data,
|
||||
arch: '<tree>' +
|
||||
'<field name="foo"/>' +
|
||||
'<field name="m2o" optional="hide"/>' +
|
||||
'<field name="amount"/>' +
|
||||
'<field name="reference" optional="hide"/>' +
|
||||
'</tree>',
|
||||
services: {
|
||||
local_storage: RamStorageService,
|
||||
},
|
||||
});
|
||||
|
||||
assert.containsN(list, 'th', 3,
|
||||
"should have 3 th, 1 for selector, 2 for columns");
|
||||
|
||||
// enable optional field
|
||||
await testUtils.dom.click(list.$('table .o_optional_columns_dropdown_toggle'));
|
||||
assert.notOk(list.$('div.o_optional_columns div.dropdown-item [name="m2o"]').is(":checked"));
|
||||
await testUtils.dom.click(list.$('div.o_optional_columns div.dropdown-item:first input'));
|
||||
assert.containsN(list, 'th', 4,
|
||||
"should have 4 th 1 for selector, 3 for columns");
|
||||
assert.ok(list.$('th:contains(M2O field)').is(':visible'),
|
||||
"should have a visible m2o field"); //m2o field
|
||||
|
||||
// reload listview
|
||||
await list.reload();
|
||||
assert.containsN(list, 'th', 4,
|
||||
"should have 4 th 1 for selector, 3 for columns ever after listview reload");
|
||||
assert.ok(list.$('th:contains(M2O field)').is(':visible'),
|
||||
"should have a visible m2o field even after listview reload");
|
||||
|
||||
await testUtils.dom.click(list.$('table .o_optional_columns_dropdown_toggle'));
|
||||
assert.ok(list.$('div.o_optional_columns div.dropdown-item [name="m2o"]').is(":checked"));
|
||||
|
||||
list.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('change the viewType of the current action', async function (assert) {
|
||||
assert.expect(25);
|
||||
|
||||
this.actions = [{
|
||||
id: 1,
|
||||
name: 'Partners Action 1',
|
||||
res_model: 'foo',
|
||||
type: 'ir.actions.act_window',
|
||||
views: [[1, 'kanban']],
|
||||
}, {
|
||||
id: 2,
|
||||
name: 'Partners',
|
||||
res_model: 'foo',
|
||||
type: 'ir.actions.act_window',
|
||||
views: [[false, 'list'], [1, 'kanban']],
|
||||
}];
|
||||
|
||||
this.archs = {
|
||||
'foo,1,kanban': '<kanban><templates><t t-name="kanban-box">' +
|
||||
'<div class="oe_kanban_global_click"><field name="foo"/></div>' +
|
||||
'</t></templates></kanban>',
|
||||
|
||||
'foo,false,list': '<tree limit="3">' +
|
||||
'<field name="foo"/>' +
|
||||
'<field name="m2o" optional="hide"/>' +
|
||||
'<field name="o2m" optional="show"/></tree>',
|
||||
|
||||
'foo,false,search': '<search><field name="foo" string="Foo"/></search>',
|
||||
};
|
||||
|
||||
var RamStorageService = AbstractStorageService.extend({
|
||||
storage: new RamStorage(),
|
||||
});
|
||||
|
||||
var actionManager = await testUtils.createActionManager({
|
||||
actions: this.actions,
|
||||
archs: this.archs,
|
||||
data: this.data,
|
||||
services: {
|
||||
local_storage: RamStorageService,
|
||||
},
|
||||
});
|
||||
await actionManager.doAction(2);
|
||||
|
||||
assert.containsOnce(actionManager, '.o_list_view',
|
||||
"should have rendered a list view");
|
||||
|
||||
assert.containsN(actionManager, 'th', 3, "should display 3 th (selector + 2 fields)");
|
||||
|
||||
// enable optional field
|
||||
await testUtils.dom.click(actionManager.$('table .o_optional_columns_dropdown_toggle'));
|
||||
assert.notOk(actionManager.$('div.o_optional_columns div.dropdown-item [name="m2o"]').is(":checked"));
|
||||
assert.ok(actionManager.$('div.o_optional_columns div.dropdown-item [name="o2m"]').is(":checked"));
|
||||
await testUtils.dom.click(actionManager.$('div.o_optional_columns div.dropdown-item:first'));
|
||||
assert.containsN(actionManager, 'th', 4, "should display 4 th (selector + 3 fields)");
|
||||
assert.ok(actionManager.$('th:contains(M2O field)').is(':visible'),
|
||||
"should have a visible m2o field"); //m2o field
|
||||
|
||||
// switch to kanban view
|
||||
await actionManager.loadState({
|
||||
action: 2,
|
||||
view_type: 'kanban',
|
||||
});
|
||||
|
||||
assert.containsNone(actionManager, '.o_list_view',
|
||||
"should not display the list view anymore");
|
||||
assert.containsOnce(actionManager, '.o_kanban_view',
|
||||
"should have switched to the kanban view");
|
||||
|
||||
// switch back to list view
|
||||
await actionManager.loadState({
|
||||
action: 2,
|
||||
view_type: 'list',
|
||||
});
|
||||
|
||||
assert.containsNone(actionManager, '.o_kanban_view',
|
||||
"should not display the kanban view anymoe");
|
||||
assert.containsOnce(actionManager, '.o_list_view',
|
||||
"should display the list view");
|
||||
|
||||
assert.containsN(actionManager, 'th', 4, "should display 4 th");
|
||||
assert.ok(actionManager.$('th:contains(M2O field)').is(':visible'),
|
||||
"should have a visible m2o field"); //m2o field
|
||||
assert.ok(actionManager.$('th:contains(O2M field)').is(':visible'),
|
||||
"should have a visible o2m field"); //m2o field
|
||||
|
||||
// disable optional field
|
||||
await testUtils.dom.click(actionManager.$('table .o_optional_columns_dropdown_toggle'));
|
||||
assert.ok(actionManager.$('div.o_optional_columns div.dropdown-item [name="m2o"]').is(":checked"));
|
||||
assert.ok(actionManager.$('div.o_optional_columns div.dropdown-item [name="o2m"]').is(":checked"));
|
||||
await testUtils.dom.click(actionManager.$('div.o_optional_columns div.dropdown-item:last input'));
|
||||
assert.ok(actionManager.$('th:contains(M2O field)').is(':visible'),
|
||||
"should have a visible m2o field"); //m2o field
|
||||
assert.notOk(actionManager.$('th:contains(O2M field)').is(':visible'),
|
||||
"should have a visible o2m field"); //m2o field
|
||||
assert.containsN(actionManager, 'th', 3, "should display 3 th");
|
||||
|
||||
await actionManager.doAction(1);
|
||||
|
||||
assert.containsNone(actionManager, '.o_list_view',
|
||||
"should not display the list view anymore");
|
||||
assert.containsOnce(actionManager, '.o_kanban_view',
|
||||
"should have switched to the kanban view");
|
||||
|
||||
await actionManager.doAction(2);
|
||||
|
||||
assert.containsNone(actionManager, '.o_kanban_view',
|
||||
"should not havethe kanban view anymoe");
|
||||
assert.containsOnce(actionManager, '.o_list_view',
|
||||
"should display the list view");
|
||||
|
||||
assert.containsN(actionManager, 'th', 3, "should display 3 th");
|
||||
assert.ok(actionManager.$('th:contains(M2O field)').is(':visible'),
|
||||
"should have a visible m2o field"); //m2o field
|
||||
assert.notOk(actionManager.$('th:contains(O2M field)').is(':visible'),
|
||||
"should have a visible o2m field"); //m2o field
|
||||
|
||||
actionManager.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('list view with optional fields rendering and local storage mock', async function (assert) {
|
||||
assert.expect(12);
|
||||
|
||||
var forceLocalStorage = true;
|
||||
|
||||
var Storage = RamStorage.extend({
|
||||
getItem: function (key) {
|
||||
assert.step('getItem ' + key);
|
||||
return forceLocalStorage ? '["m2o"]' : this._super.apply(this, arguments);
|
||||
},
|
||||
setItem: function (key, value) {
|
||||
assert.step('setItem ' + key + ' to ' + value);
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
var RamStorageService = AbstractStorageService.extend({
|
||||
storage: new Storage(),
|
||||
});
|
||||
|
||||
var list = await createView({
|
||||
View: ListView,
|
||||
model: 'foo',
|
||||
data: this.data,
|
||||
arch: '<tree>' +
|
||||
'<field name="foo"/>' +
|
||||
'<field name="m2o" optional="hide"/>' +
|
||||
'<field name="reference" optional="show"/>' +
|
||||
'</tree>',
|
||||
services: {
|
||||
local_storage: RamStorageService,
|
||||
},
|
||||
});
|
||||
|
||||
assert.verifySteps(['getItem list_optional_fields,foo,list,foo:char,m2o:many2one,reference:reference']);
|
||||
|
||||
assert.containsN(list, 'th', 3,
|
||||
"should have 3 th, 1 for selector, 2 for columns");
|
||||
|
||||
assert.ok(list.$('th:contains(M2O field)').is(':visible'),
|
||||
"should have a visible m2o field"); //m2o field
|
||||
|
||||
assert.notOk(list.$('th:contains(Reference Field)').is(':visible'),
|
||||
"should not have a visible reference field");
|
||||
|
||||
// optional fields
|
||||
await testUtils.dom.click(list.$('table .o_optional_columns_dropdown_toggle'));
|
||||
assert.containsN(list, 'div.o_optional_columns div.dropdown-item', 2,
|
||||
"dropdown have 2 optional fields");
|
||||
|
||||
forceLocalStorage = false;
|
||||
// enable optional field
|
||||
await testUtils.dom.click(list.$('div.o_optional_columns div.dropdown-item:eq(1) input'));
|
||||
|
||||
assert.verifySteps([
|
||||
'setItem list_optional_fields,foo,list,foo:char,m2o:many2one,reference:reference to ["m2o","reference"]',
|
||||
'getItem list_optional_fields,foo,list,foo:char,m2o:many2one,reference:reference',
|
||||
]);
|
||||
|
||||
// 4 th (1 for checkbox, 3 for columns)
|
||||
assert.containsN(list, 'th', 4, "should have 4 th");
|
||||
|
||||
assert.ok(list.$('th:contains(M2O field)').is(':visible'),
|
||||
"should have a visible m2o field"); //m2o field
|
||||
|
||||
assert.ok(list.$('th:contains(Reference Field)').is(':visible'),
|
||||
"should have a visible reference field");
|
||||
|
||||
list.destroy();
|
||||
});
|
||||
|
||||
// TODO: write test on:
|
||||
// - default_get with a field not in view
|
||||
});
|
||||
|
||||
@@ -350,7 +350,7 @@ QUnit.module('Views', {
|
||||
'There should be two modals');
|
||||
|
||||
var $second_modal = $modals.not($modal);
|
||||
await testUtils.dom.click($second_modal.find('.o_list_view.table.table-sm.table-striped.o_list_view_ungrouped .o_data_row input[type=checkbox]'));
|
||||
await testUtils.dom.click($second_modal.find('.o_list_table.table.table-sm.table-striped.o_list_table_ungrouped .o_data_row input[type=checkbox]'));
|
||||
|
||||
await testUtils.dom.click($second_modal.find('.o_select_button'));
|
||||
|
||||
|
||||
@@ -261,6 +261,7 @@
|
||||
<rng:optional><rng:attribute name="write_model" /></rng:optional>
|
||||
<rng:optional><rng:attribute name="write_field" /></rng:optional>
|
||||
<rng:optional><rng:attribute name="text" /></rng:optional>
|
||||
<rng:optional><rng:attribute name="optional" /></rng:optional>
|
||||
<rng:optional>
|
||||
<rng:attribute name="force_save">
|
||||
<rng:choice>
|
||||
|
||||
@@ -152,7 +152,7 @@ tour.register('main_flow_tour', {
|
||||
{
|
||||
// Add second component
|
||||
trigger: ".o_field_x2many_list_row_add > a",
|
||||
extra_trigger: "body:not(:has(table.o_list_view div.o_field_widget[name='product_id'] input))",
|
||||
extra_trigger: "body:not(:has(table.o_list_table div.o_field_widget[name='product_id'] input))",
|
||||
content: _t("Click here to add some lines."),
|
||||
position: "bottom",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user