[IMP] web: control_panel dropdown forms design

This commit is contained in:
stefanorigano (SRI)
2021-06-18 21:31:32 +02:00
committed by Aaron Bohy
parent cfca13199e
commit 490acfcbb7
8 changed files with 154 additions and 134 deletions
@@ -82,34 +82,45 @@
// Filter menu
.o_filter_menu {
.o_filter_condition {
margin-bottom: 8px;
position: relative;
.o_or_filter { // or between conditions
@include o-position-absolute($top: 3px, $left: 4px);
&.o_filter_condition_with_buttons {
padding-right: 0;
padding-left: $dropdown-item-padding-x*1.5;
}
.o_generator_menu_operator {
margin: 3px 0px; // vertical gap between selects
.o_or_filter {
@include o-position-absolute($left: $dropdown-item-padding-x*.5);
}
.o_generator_menu_value { // date fields: datepicker position
.o_generator_menu_value {
color: $o-main-text-color; // avoid to inherit .dropdown-item style
.datepickerbutton {
cursor: pointer;
@include o-position-absolute(3px, -20px);
}
}
.o_generator_menu_delete { // delete condition button position
cursor: pointer;
@include o-position-absolute($top: 6px, $right: 4px);
.o_generator_menu_delete {
@include o-hover-opacity;
&:before {
@include o-position-absolute(0, 0, 0, 0);
pointer-events: none;
z-index: -1;
content: "";
}
&:hover:before {
background-color: gray('100');
}
}
}
.o_add_condition {
line-height: 1.1;
.o_add_filter_menu { // apply and add buttons
display: flex;
justify-content: space-between;
.fa {
font-size: $font-size-lg;
}
}
}
+106 -97
View File
@@ -255,7 +255,13 @@
</ul>
</t>
<a t-else="" class="dropdown-item"
t-att-class="{ selected: props.isActive, [props.className]: props.className }"
t-att-class="{
selected: props.isActive,
[props.className]: props.className,
'd-flex': props.removable,
'align-items-center': props.removable,
'justify-content-between': props.removable
}"
t-att-aria-checked="props.isActive ? 'true' : 'false'"
role="menuitemcheckbox"
t-att-href="props.url or '#'"
@@ -263,7 +269,7 @@
>
<t t-esc="props.description"/>
<i t-if="props.removable"
class="o_icon_right fa fa-trash-o"
class="o_icon_right fa fa-trash btn btn-link text-danger p-0 mr-n3"
title="Delete item"
t-on-click.stop.prevent="trigger('remove-item', { item: props })"
/>
@@ -273,18 +279,20 @@
<t t-name="web.CustomFavoriteItem" owl="1">
<li class="o_menu_item o_add_favorite" role="menuitem">
<button type="button"
<span type="button"
class="dropdown-item"
t-ref="fallback-focus"
t-on-click="state.open = !state.open"
t-att-class="{ 'active': state.open, 'o_dropdown_item_active_noarrow' : state.open}"
>
<t>Save current search</t>
</button>
</span>
<div t-if="state.open" class="dropdown-item-text">
<div role="menuitem" class="dropdown-item-text">
<div class="my-2">
<input type="text"
class="o_input"
autofocus=""
placeholder="My custom search"
t-ref="description"
t-model.trim="state.description"
t-on-keydown="_onInputKeydown"
@@ -310,10 +318,9 @@
/>
<label class="custom-control-label" t-att-for="shareAllUsersId">
<t>Share with all users</t>
<i class="fa fa-users" role="img" aria-label="Users" title="Users"/>
</label>
</div>
<div class="dropdown-item-text">
<div class="mt-3">
<button type="button" class="o_save_favorite btn btn-primary" t-on-click="_saveFavorite">Save</button>
</div>
</div>
@@ -322,109 +329,110 @@
<t t-name="web.CustomFilterItem" owl="1">
<div class="o_generator_menu">
<button type="button"
class="o_add_custom_filter dropdown-item"
<span class="o_add_custom_filter dropdown-item"
aria-expanded="false"
t-ref="fallback-focus"
t-on-click="state.open = !state.open"
t-on-keydown="_onKeydown"
t-att-class="{ 'active': state.open, 'o_dropdown_item_active_noarrow' : state.open}"
>
<t>Add Custom Filter</t>
</button>
</span>
<t t-if="state.open">
<t t-set="showRemoveIcons" t-value="state.conditions.length gt 1"/>
<div t-foreach="state.conditions" t-as="condition" t-key="condition_index"
class=" o_filter_condition dropdown-item-text"
>
class="o_filter_condition dropdown-item-text position-relative d-flex align-items-center py-2 mb-2"
t-att-class="{ 'o_filter_condition_with_buttons': showRemoveIcons }">
<t t-set="fieldType" t-value="fields[condition.field].type"/>
<t t-set="selectedOperator" t-value="OPERATORS[FIELD_TYPES[fieldType]][condition.operator]"/>
<span t-if="!condition_first" class="o_or_filter">or</span>
<select class="o_input o_generator_menu_field"
t-on-change="_onFieldSelect(condition)"
>
<option t-foreach="fields" t-as="field" t-key="field_index"
t-att-value="field.name"
t-att-selected="field_index === condition.field"
t-esc="field.string"
/>
</select>
<select class="o_input o_generator_menu_operator"
t-on-change="_onOperatorSelect(condition)"
>
<option t-foreach="OPERATORS[FIELD_TYPES[fieldType]]" t-as="operator" t-key="operator_index"
t-att-value="operator.symbol"
t-att-selected="operator_index === condition.operator"
t-esc="operator.description"
/>
</select>
<span t-if="!('value' in selectedOperator)" class="o_generator_menu_value">
<t t-if="fieldType === 'date'">
<DatePicker
date="condition.value[0]"
t-on-datetime-changed="_onDateChanged(condition, 0)"
/>
<DatePicker t-if="selectedOperator.symbol === 'between'"
date="condition.value[1]"
t-on-datetime-changed="_onDateChanged(condition, 1)"
/>
</t>
<t t-elif="fieldType === 'datetime'">
<DateTimePicker
date="condition.value[0]"
t-on-datetime-changed="_onDateChanged(condition, 0)"
/>
<DateTimePicker t-if="selectedOperator.symbol === 'between'"
date="condition.value[1]"
t-on-datetime-changed="_onDateChanged(condition, 1)"
/>
</t>
<select t-elif="fieldType === 'selection'" class="o_input"
t-on-change="_onValueInput(condition)"
<div t-if="!condition_first" class="o_or_filter text-center text-muted font-italic small">or</div>
<div class="d-flex flex-column">
<select class="o_input o_generator_menu_field pr-3"
t-on-change="_onFieldSelect(condition)"
>
<option t-foreach="fields[condition.field].selection" t-as="option" t-key="option_index"
t-att-value="option[0]"
t-esc="option[1]"
<option t-foreach="fields" t-as="field" t-key="field_index"
t-att-value="field.name"
t-att-selected="field_index === condition.field"
t-esc="field.string"
/>
</select>
<input t-elif="fieldType === 'float'"
class="o_input"
step="0.01"
t-att-type="DECIMAL_POINT === '.' ? 'number' : 'text'"
t-attf-title="Number using {{ DECIMAL_POINT }} as decimal separator."
t-attf-pattern="[0-9]+([\\{{ DECIMAL_POINT }}][0-9]+)?"
t-att-value="condition.displayedValue"
t-on-input="_onValueInput(condition)"
/>
<input t-elif="['integer', 'id'].includes(fieldType)"
class="o_input"
step="1"
type="number"
t-att-value="condition.displayedValue"
t-on-input="_onValueInput(condition)"
/>
<input t-else=""
type="text"
class="o_input"
t-att-value="condition.displayedValue"
t-on-input="_onValueInput(condition)"
/>
</span>
<i t-if="state.conditions.length gt 1"
class="fa fa-trash-o o_generator_menu_delete"
role="image"
aria-label="Delete"
title="Delete"
t-on-click="_onRemoveCondition(condition_index)"
/>
<select class="o_input o_generator_menu_operator my-1 pr-3"
t-on-change="_onOperatorSelect(condition)"
>
<option t-foreach="OPERATORS[FIELD_TYPES[fieldType]]" t-as="operator" t-key="operator_index"
t-att-value="operator.symbol"
t-att-selected="operator_index === condition.operator"
t-esc="operator.description"
/>
</select>
<span t-if="!('value' in selectedOperator)" class="o_generator_menu_value">
<t t-if="fieldType === 'date'">
<DatePicker
date="condition.value[0]"
t-on-datetime-changed="_onDateChanged(condition, 0)"
/>
<DatePicker t-if="selectedOperator.symbol === 'between'"
date="condition.value[1]"
t-on-datetime-changed="_onDateChanged(condition, 1)"
/>
</t>
<t t-elif="fieldType === 'datetime'">
<DateTimePicker
date="condition.value[0]"
t-on-datetime-changed="_onDateChanged(condition, 0)"
/>
<DateTimePicker t-if="selectedOperator.symbol === 'between'"
date="condition.value[1]"
t-on-datetime-changed="_onDateChanged(condition, 1)"
/>
</t>
<select t-elif="fieldType === 'selection'" class="o_input"
t-on-change="_onValueInput(condition)"
>
<option t-foreach="fields[condition.field].selection" t-as="option" t-key="option_index"
t-att-value="option[0]"
t-esc="option[1]"
/>
</select>
<input t-elif="fieldType === 'float'"
class="o_input"
step="0.01"
t-att-type="DECIMAL_POINT === '.' ? 'number' : 'text'"
t-attf-title="Number using {{ DECIMAL_POINT }} as decimal separator."
t-attf-pattern="[0-9]+([\{{ DECIMAL_POINT }}][0-9]+)?"
t-att-value="condition.displayedValue"
t-on-input="_onValueInput(condition)"
/>
<input t-elif="['integer', 'id'].includes(fieldType)"
class="o_input"
step="1"
type="number"
t-att-value="condition.displayedValue"
t-on-input="_onValueInput(condition)"
/>
<input t-else=""
type="text"
class="o_input"
t-att-value="condition.displayedValue"
t-on-input="_onValueInput(condition)"
/>
</span>
</div>
<button t-if="showRemoveIcons" class="o_generator_menu_delete btn btn-link text-danger"
aria-label="Delete condition"
title="Delete condition"
t-on-click="_onRemoveCondition(condition_index)">
&#215;
</button>
</div>
<div class="o_add_filter_menu dropdown-item-text">
<button type="button" class="btn btn-primary o_apply_filter" t-on-click="_onApply">
<div class="o_add_filter_menu dropdown-item-text pt-2 d-flex justify-content-between text-nowrap">
<button type="button" class="btn btn-primary btn-sm mr-2 o_apply_filter" t-on-click="_onApply">
<t>Apply</t>
</button>
<button type="button"
class="btn btn-secondary o_add_condition"
t-on-click="_addNewCondition"
>
<i class="fa fa-plus-circle"/>
<button type="button" class="o_add_condition btn btn-secondary btn-sm d-flex align-items-center text-left" t-on-click="_addNewCondition">
<i class="fa fa-plus-circle mr-2"/>
<t>Add a condition</t>
</button>
</div>
@@ -434,16 +442,17 @@
<t t-name="web.CustomGroupByItem" owl="1">
<div class="o_generator_menu">
<button type="button"
<span type="button"
class="o_add_custom_group_by dropdown-item"
aria-expanded="false"
t-ref="fallback-focus"
t-on-click="state.open = !state.open"
t-on-keydown="_onKeydown"
t-att-class="{ 'active': state.open, 'o_dropdown_item_active_noarrow' : state.open}"
>
<t>Add Custom Group</t>
</button>
<div t-if="state.open" class="dropdown-item-text">
</span>
<div t-if="state.open" class="dropdown-item-text my-2">
<select class="w-auto o_input o_group_by_selector" t-model="state.fieldName">
<option t-foreach="props.fields" t-as="field" t-key="field.name"
t-att-value="field.name"
@@ -46,9 +46,9 @@ odoo.define('web.filter_menu_generator_tests', function (require) {
// Single condition
assert.containsOnce(cfi, 'div.o_filter_condition');
assert.containsOnce(cfi, 'div.o_filter_condition > select.o_generator_menu_field');
assert.containsOnce(cfi, 'div.o_filter_condition > select.o_generator_menu_operator');
assert.containsOnce(cfi, 'div.o_filter_condition > span.o_generator_menu_value');
assert.containsOnce(cfi, 'div.o_filter_condition select.o_generator_menu_field');
assert.containsOnce(cfi, 'div.o_filter_condition select.o_generator_menu_operator');
assert.containsOnce(cfi, 'div.o_filter_condition span.o_generator_menu_value');
assert.containsNone(cfi, 'div.o_filter_condition .o_or_filter');
assert.containsNone(cfi, 'div.o_filter_condition .o_generator_menu_delete');
@@ -228,7 +228,7 @@ odoo.define('web.filter_menu_generator_tests', function (require) {
// The only thing visible should be the button 'Add Custome Filter';
assert.strictEqual(cfi.el.children.length, 1);
assert.containsOnce(cfi, 'button.o_add_custom_filter');
assert.containsOnce(cfi, 'span.o_add_custom_filter');
cfi.destroy();
});
@@ -267,7 +267,7 @@ odoo.define('web.filter_menu_generator_tests', function (require) {
await testUtils.fields.editSelect(cfi.el.querySelector('select.o_generator_menu_field'), 'id_field');
await testUtils.fields.editSelect(cfi.el.querySelector('.o_generator_menu_operator'), operator);
await testUtils.fields.editInput(cfi.el.querySelector(
'div.o_filter_condition > span.o_generator_menu_value input'),
'div.o_filter_condition span.o_generator_menu_value input'),
value
);
await cpHelpers.applyFilter(cfi);
@@ -319,7 +319,7 @@ odoo.define('web.filter_menu_generator_tests', function (require) {
await cpHelpers.toggleAddCustomFilter(cfi);
await testUtils.fields.editSelect(cfi.el.querySelector('select.o_generator_menu_field'), 'many2one_field');
await testUtils.fields.editInput(cfi.el.querySelector(
'div.o_filter_condition > span.o_generator_menu_value input'),
'div.o_filter_condition span.o_generator_menu_value input'),
value
);
await cpHelpers.applyFilter(cfi);
@@ -369,7 +369,7 @@ odoo.define('web.filter_menu_generator_tests', function (require) {
assert.strictEqual(cfi.el.querySelector('.o_generator_menu_operator').value, 'between');
await testUtils.fields.editSelect(cfi.el.querySelector('.o_generator_menu_operator'), '=');
await testUtils.fields.editSelect(cfi.el.querySelector('div.o_filter_condition > span.o_generator_menu_value input'), '02/22/2017 11:00:00'); // in TZ
await testUtils.fields.editSelect(cfi.el.querySelector('div.o_filter_condition span.o_generator_menu_value input'), '02/22/2017 11:00:00'); // in TZ
await cpHelpers.applyFilter(cfi);
cfi.destroy();
@@ -29,7 +29,7 @@ odoo.define('web.groupby_menu_generator_tests', function (require) {
assert.hasClass(cgi.el, 'o_generator_menu');
assert.strictEqual(cgi.el.children.length, 1);
await testUtils.dom.click(cgi.el.querySelector('.o_generator_menu button.o_add_custom_group_by'));
await testUtils.dom.click(cgi.el.querySelector('.o_generator_menu span.o_add_custom_group_by'));
// Single select node with a single option
assert.containsOnce(cgi, 'div > select.o_group_by_selector');
@@ -61,12 +61,12 @@ odoo.define('web.groupby_menu_generator_tests', function (require) {
env: { searchModel },
});
await testUtils.dom.click(cgi.el.querySelector('.o_generator_menu button.o_add_custom_group_by'));
await testUtils.dom.click(cgi.el.querySelector('.o_generator_menu span.o_add_custom_group_by'));
await testUtils.dom.click(cgi.el.querySelector('.o_generator_menu button.o_apply_group_by'));
// The only thing visible should be the button 'Add Custome Group';
assert.strictEqual(cgi.el.children.length, 1);
assert.containsOnce(cgi, 'button.o_add_custom_group_by');
assert.containsOnce(cgi, 'span.o_add_custom_group_by');
cgi.destroy();
});
@@ -39,7 +39,7 @@ odoo.define('web.favorite_menu_tests', function (require) {
await cpHelpers.toggleFavoriteMenu(controlPanel);
assert.containsNone(controlPanel, '.dropdown-divider');
assert.containsOnce(controlPanel, '.o_add_favorite');
assert.strictEqual(controlPanel.el.querySelector('.o_add_favorite > button').innerText.trim(),
assert.strictEqual(controlPanel.el.querySelector('.o_add_favorite > span').innerText.trim(),
"Save current search");
await cpHelpers.toggleSaveFavorite(controlPanel);
@@ -67,7 +67,7 @@ odoo.define('web.filter_menu_tests', function (require) {
await cpHelpers.toggleFilterMenu(controlPanel);
await cpHelpers.toggleAddCustomFilter(controlPanel);
const optionEls = controlPanel.el.querySelectorAll('div.o_filter_condition > select.o_generator_menu_field option');
const optionEls = controlPanel.el.querySelectorAll('div.o_filter_condition select.o_generator_menu_field option');
assert.strictEqual(optionEls[0].innerText.trim(), 'Date');
assert.strictEqual(optionEls[1].innerText.trim(), 'ID');
@@ -127,9 +127,9 @@ odoo.define('web.filter_menu_tests', function (require) {
await cpHelpers.toggleAddCustomFilter(controlPanel);
// choose ID field in 'Add Custome filter' menu and value 1
await testUtils.fields.editSelect(
controlPanel.el.querySelector('div.o_filter_condition > select.o_generator_menu_field'), 'id');
controlPanel.el.querySelector('div.o_filter_condition select.o_generator_menu_field'), 'id');
await testUtils.fields.editInput(
controlPanel.el.querySelector('div.o_filter_condition > span.o_generator_menu_value > input'), 1);
controlPanel.el.querySelector('div.o_filter_condition span.o_generator_menu_value > input'), 1);
await cpHelpers.applyFilter(controlPanel);
@@ -93,7 +93,7 @@ odoo.define('web.test_utils_control_panel', function (require) {
* @returns {Promise}
*/
async function toggleAddCustomFilter(el) {
await click(getNode(el).querySelector(`button.o_add_custom_filter`));
await click(getNode(el).querySelector(`span.o_add_custom_filter`));
}
/**
@@ -125,7 +125,7 @@ odoo.define('web.test_utils_control_panel', function (require) {
* @returns {Promise}
*/
async function toggleAddCustomGroup(el) {
await click(getNode(el).querySelector(`button.o_add_custom_group_by`));
await click(getNode(el).querySelector(`span.o_add_custom_group_by`));
}
/**
@@ -161,7 +161,7 @@ odoo.define('web.test_utils_control_panel', function (require) {
* @returns {Promise}
*/
async function toggleSaveFavorite(el) {
await click(getNode(el).querySelector(`.o_favorite_menu .o_add_favorite button`));
await click(getNode(el).querySelector(`.o_favorite_menu .o_add_favorite .dropdown-item`));
}
/**
@@ -188,7 +188,7 @@ odoo.define('web.test_utils_control_panel', function (require) {
*/
async function deleteFavorite(el, favoriteFinder) {
const favorite = findItem(el, `.o_favorite_menu .o_menu_item`, favoriteFinder);
await click(favorite.querySelector('i.fa-trash-o'));
await click(favorite.querySelector('i.fa-trash'));
}
/**
@@ -755,7 +755,7 @@ QUnit.module('Views', {
assert.containsOnce(pivot, '.o_pivot_field_menu .o_generator_menu',
"should have custom group generator same as searchview groupby");
// check custom groupby selection has groupable fields only
await testUtils.dom.click(pivot.$('.o_pivot_field_menu .o_generator_menu button.o_add_custom_group_by'));
await testUtils.dom.click(pivot.$('.o_pivot_field_menu .o_generator_menu .o_add_custom_group_by'));
assert.containsN(pivot, '.o_pivot_field_menu .o_generator_menu .o_group_by_selector option', 6,
"should have 3 fields in custom groupby");
const optionDescriptions = [...pivot.$('.o_pivot_field_menu .o_generator_menu .o_group_by_selector option')]
@@ -818,7 +818,7 @@ QUnit.module('Views', {
assert.containsN(pivot, '.o_pivot_field_menu > .dropdown-item', 3,
"should have 3 dropdown items same as searchview groupby");
await testUtils.dom.click(pivot.$('.o_pivot_field_menu .o_generator_menu button.o_add_custom_group_by'));
await testUtils.dom.click(pivot.$('.o_pivot_field_menu .o_generator_menu .o_add_custom_group_by'));
await testUtils.fields.editSelect(pivot.$('.o_pivot_field_menu .o_generator_menu .o_group_by_selector'), 'date');
await testUtils.dom.click(pivot.$('.o_pivot_field_menu .o_generator_menu button.o_apply_group_by'));
// click on closed header to open groupby selection dropdown
@@ -910,7 +910,7 @@ QUnit.module('Views', {
// click on closed header to open dropdown and apply groupby on date field
await testUtils.dom.click(pivot.$('thead .o_pivot_header_cell_closed'));
await testUtils.dom.click(pivot.$('.o_pivot_field_menu .o_generator_menu button.o_add_custom_group_by'));
await testUtils.dom.click(pivot.$('.o_pivot_field_menu .o_generator_menu .o_add_custom_group_by'));
await testUtils.fields.editSelect(pivot.$('.o_pivot_field_menu .o_generator_menu .o_group_by_selector'), 'date');
checkReadGroup = true;
await testUtils.dom.click(pivot.$('.o_pivot_field_menu .o_generator_menu button.o_apply_group_by'));
@@ -1021,7 +1021,7 @@ QUnit.module('Views', {
// click on closed header to open dropdown and apply groupby on date field
await testUtils.dom.click(pivot.$('thead .o_pivot_header_cell_closed'));
await testUtils.dom.click(pivot.$('.o_pivot_field_menu .o_generator_menu button.o_add_custom_group_by'));
await testUtils.dom.click(pivot.$('.o_pivot_field_menu .o_generator_menu .o_add_custom_group_by'));
assert.containsOnce(pivot, '.o_pivot_field_menu .o_generator_menu',
"should have custom group generator in groupby dropdown");