mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-20 23:32:03 +02:00
19.0 vanilla
This commit is contained in:
parent
d1963a3c3a
commit
2d3ee4855a
7430 changed files with 2687981 additions and 2965473 deletions
|
|
@ -1,91 +0,0 @@
|
|||
|
||||
odoo.define('resource.section_backend', function (require) {
|
||||
// The goal of this file is to contain JS hacks related to allowing
|
||||
// section on resource calendar.
|
||||
|
||||
"use strict";
|
||||
var FieldOne2Many = require('web.relational_fields').FieldOne2Many;
|
||||
var fieldRegistry = require('web.field_registry');
|
||||
var ListRenderer = require('web.ListRenderer');
|
||||
|
||||
var SectionListRenderer = ListRenderer.extend({
|
||||
/**
|
||||
* We want section to take the whole line (except handle and trash)
|
||||
* to look better and to hide the unnecessary fields.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_renderBodyCell: function (record, node, index, options) {
|
||||
var $cell = this._super.apply(this, arguments);
|
||||
|
||||
var isSection = record.data.display_type === 'line_section';
|
||||
|
||||
if (isSection) {
|
||||
if (node.attrs.widget === "handle") {
|
||||
return $cell;
|
||||
} else if (node.attrs.name === "display_name") {
|
||||
var nbrColumns = this._getNumberOfCols();
|
||||
if (this.handleField) {
|
||||
nbrColumns--;
|
||||
}
|
||||
if (this.addTrashIcon) {
|
||||
nbrColumns--;
|
||||
}
|
||||
$cell.attr('colspan', nbrColumns);
|
||||
} else {
|
||||
return $cell.addClass('o_hidden');
|
||||
}
|
||||
}
|
||||
|
||||
return $cell;
|
||||
},
|
||||
/**
|
||||
* We add the o_is_{display_type} class to allow custom behaviour both in JS and CSS.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_renderRow: function (record, index) {
|
||||
var $row = this._super.apply(this, arguments);
|
||||
|
||||
if (record.data.display_type) {
|
||||
$row.addClass('o_is_' + record.data.display_type);
|
||||
}
|
||||
|
||||
return $row;
|
||||
},
|
||||
/**
|
||||
* We want to add .o_section_list_view on the table to have stronger CSS.
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_renderView: function () {
|
||||
var self = this;
|
||||
return this._super.apply(this, arguments).then(function () {
|
||||
self.$('.o_list_table').addClass('o_section_list_view');
|
||||
// Discard the possibility to remove the sections
|
||||
self.$('.o_is_line_section .o_list_record_remove').remove()
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// We create a custom widget because this is the cleanest way to do it:
|
||||
// to be sure this custom code will only impact selected fields having the widget
|
||||
// and not applied to any other existing ListRenderer.
|
||||
var SectionFieldOne2Many = FieldOne2Many.extend({
|
||||
/**
|
||||
* We want to use our custom renderer for the list.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_getRenderer: function () {
|
||||
if (this.view.arch.tag === 'tree') {
|
||||
return SectionListRenderer;
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
fieldRegistry.add('section_one2many', SectionFieldOne2Many);
|
||||
|
||||
});
|
||||
|
|
@ -1,8 +1,5 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { ListRenderer } from "@web/views/list/list_renderer";
|
||||
|
||||
const { useEffect } = owl;
|
||||
import { useEffect } from "@odoo/owl";
|
||||
|
||||
export class SectionListRenderer extends ListRenderer {
|
||||
setup() {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="resource.SectionListRenderer.RecordRow" t-inherit="web.ListRenderer.RecordRow" owl="1">
|
||||
<xpath expr="//t[@t-if='displayOptionalFields or hasX2ManyAction']" position="attributes">
|
||||
<t t-name="resource.SectionListRenderer.RecordRow" t-inherit="web.ListRenderer.RecordRow">
|
||||
<xpath expr="//t[@t-if='displayOptionalFields or props.list.isGrouped or hasX2ManyAction']" position="attributes">
|
||||
<attribute name="t-if">(displayOptionalFields or hasX2ManyAction) and !isSection(record)</attribute>
|
||||
</xpath>
|
||||
</t>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,20 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { SectionListRenderer } from "./section_list_renderer";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { X2ManyField } from "@web/views/fields/x2many/x2many_field";
|
||||
import { X2ManyField, x2ManyField } from "@web/views/fields/x2many/x2many_field";
|
||||
|
||||
class SectionOneToManyField extends X2ManyField {}
|
||||
SectionOneToManyField.components = {
|
||||
...X2ManyField.components,
|
||||
ListRenderer: SectionListRenderer,
|
||||
};
|
||||
SectionOneToManyField.defaultProps = {
|
||||
...X2ManyField.defaultProps,
|
||||
editable: "bottom",
|
||||
};
|
||||
class SectionOneToManyField extends X2ManyField {
|
||||
static components = {
|
||||
...X2ManyField.components,
|
||||
ListRenderer: SectionListRenderer,
|
||||
};
|
||||
static defaultProps = {
|
||||
...X2ManyField.defaultProps,
|
||||
editable: "bottom",
|
||||
};
|
||||
}
|
||||
|
||||
SectionOneToManyField.additionalClasses = ['o_field_one2many'];
|
||||
registry.category("fields").add("section_one2many", SectionOneToManyField);
|
||||
registry.category("fields").add("section_one2many", {
|
||||
...x2ManyField,
|
||||
component: SectionOneToManyField,
|
||||
additionalClasses: [...x2ManyField.additionalClasses || [], "o_field_one2many"],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
import { useState } from "@odoo/owl";
|
||||
import { FormController } from "@web/views/form/form_controller";
|
||||
|
||||
export class FormControllerWithHTMLExpander extends FormController {
|
||||
static template = "resource.FormViewWithHtmlExpander";
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
this.htmlExpanderState = useState({ reload: true });
|
||||
const oldOnNotebookPageChange = this.onNotebookPageChange;
|
||||
this.onNotebookPageChange = (notebookId, page) => {
|
||||
oldOnNotebookPageChange(notebookId, page);
|
||||
if (page && !this.htmlExpanderState.reload) {
|
||||
this.htmlExpanderState.reload = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
get modelParams() {
|
||||
const modelParams = super.modelParams;
|
||||
const onRootLoaded = modelParams.hooks.onRootLoaded;
|
||||
modelParams.hooks.onRootLoaded = async () => {
|
||||
if (onRootLoaded) {
|
||||
onRootLoaded();
|
||||
}
|
||||
this.htmlExpanderState.reload = true;
|
||||
};
|
||||
return modelParams;
|
||||
}
|
||||
|
||||
notifyHTMLFieldExpanded() {
|
||||
this.htmlExpanderState.reload = false;
|
||||
}
|
||||
|
||||
async onRecordSaved(record, changes) {
|
||||
super.onRecordSaved(record, changes);
|
||||
this.htmlExpanderState.reload = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="resource.FormViewWithHtmlExpander" t-inherit="web.FormView">
|
||||
<xpath expr="//Layout/t[@t-component='props.Renderer']" position="attributes">
|
||||
<attribute name="reloadHtmlFieldHeight">htmlExpanderState.reload</attribute>
|
||||
<attribute name="notifyHtmlExpander.bind">notifyHTMLFieldExpanded</attribute>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
import { useService } from "@web/core/utils/hooks";
|
||||
import { FormRenderer } from "@web/views/form/form_renderer";
|
||||
import { useRef, useEffect } from "@odoo/owl";
|
||||
|
||||
export class FormRendererWithHtmlExpander extends FormRenderer {
|
||||
static props = {
|
||||
...FormRenderer.props,
|
||||
reloadHtmlFieldHeight: { type: Boolean, optional: true },
|
||||
notifyHtmlExpander: { type: Function, optional: true },
|
||||
};
|
||||
static defaultProps = {
|
||||
...FormRenderer.defaultProps,
|
||||
reloadHtmlFieldHeight: true,
|
||||
notifyHtmlExpander: () => {},
|
||||
};
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
if (!this.uiService) {
|
||||
// Should be defined in FormRenderer
|
||||
this.uiService = useService("ui");
|
||||
}
|
||||
const ref = useRef("compiled_view_root");
|
||||
useEffect(
|
||||
(el, size) => {
|
||||
if (el && this._canExpandHTMLField(size)) {
|
||||
const descriptionField = el.querySelector(this.htmlFieldQuerySelector);
|
||||
if (descriptionField) {
|
||||
const containerEL = descriptionField.closest(
|
||||
this.getHTMLFieldContainerQuerySelector
|
||||
);
|
||||
const editor = descriptionField.querySelector(".note-editable");
|
||||
const elementToResize = editor || descriptionField;
|
||||
const { top, bottom } = elementToResize.getBoundingClientRect();
|
||||
const { bottom: containerBottom } = containerEL.getBoundingClientRect();
|
||||
const { paddingTop, paddingBottom } = window.getComputedStyle(containerEL);
|
||||
const nonEditableHeight =
|
||||
containerBottom -
|
||||
bottom +
|
||||
parseInt(paddingTop) +
|
||||
parseInt(paddingBottom);
|
||||
const minHeight =
|
||||
document.documentElement.clientHeight - top - nonEditableHeight;
|
||||
elementToResize.style.minHeight = `${minHeight}px`;
|
||||
}
|
||||
}
|
||||
this.props.notifyHtmlExpander();
|
||||
},
|
||||
() => [ref.el, this.uiService.size, this.props.reloadHtmlFieldHeight]
|
||||
);
|
||||
}
|
||||
|
||||
get htmlFieldQuerySelector() {
|
||||
return ".o_field_html[name=description]";
|
||||
}
|
||||
|
||||
get getHTMLFieldContainerQuerySelector() {
|
||||
return ".o_form_sheet";
|
||||
}
|
||||
|
||||
_canExpandHTMLField(size) {
|
||||
return size === 6;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { registry } from "@web/core/registry";
|
||||
import { formView } from "@web/views/form/form_view";
|
||||
import { FormRendererWithHtmlExpander } from "./form_renderer_with_html_expander";
|
||||
import { FormControllerWithHTMLExpander } from "./form_controller_with_html_expander";
|
||||
|
||||
export const formViewWithHtmlExpander = {
|
||||
...formView,
|
||||
Controller: FormControllerWithHTMLExpander,
|
||||
Renderer: FormRendererWithHtmlExpander,
|
||||
};
|
||||
|
||||
registry.category("views").add("form_description_expander", formViewWithHtmlExpander);
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
import { expect, test } from "@odoo/hoot";
|
||||
import { defineModels, fields, models, mountView } from "@web/../tests/web_test_helpers";
|
||||
|
||||
class Partner extends models.Model {
|
||||
lines = fields.One2many({ relation: "lines_sections" });
|
||||
_records = [
|
||||
{
|
||||
id: 1,
|
||||
lines: [1, 2],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
class LinesSections extends models.Model {
|
||||
_name = "lines_sections";
|
||||
|
||||
display_type = fields.Char();
|
||||
title = fields.Char();
|
||||
int = fields.Integer();
|
||||
|
||||
_records = [
|
||||
{
|
||||
id: 1,
|
||||
display_type: "line_section",
|
||||
title: "firstSectionTitle",
|
||||
int: 4,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
display_type: false,
|
||||
title: "recordTitle",
|
||||
int: 5,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
defineModels([Partner, LinesSections]);
|
||||
|
||||
test("basic rendering", async () => {
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="lines" widget="section_one2many">
|
||||
<list>
|
||||
<field name="display_type" column_invisible="1" />
|
||||
<field name="title" />
|
||||
<field name="int" />
|
||||
</list>
|
||||
</field>
|
||||
</form>
|
||||
`,
|
||||
});
|
||||
expect(".o_field_x2many .o_list_renderer table.o_section_list_view").toHaveCount(1);
|
||||
expect(".o_data_row").toHaveCount(2);
|
||||
expect(".o_data_row:first").toHaveClass("o_is_line_section fw-bold");
|
||||
expect(".o_data_row:eq(1)").not.toHaveClass("o_is_line_section fw-bold");
|
||||
expect(".o_data_row:first").toHaveText("firstSectionTitle");
|
||||
expect(".o_data_row:eq(1)").toHaveText("recordTitle 5");
|
||||
expect(".o_data_row:first td[name=title]").toHaveAttribute("colspan", "3");
|
||||
expect(".o_data_row:eq(1) td[name=title]").not.toHaveAttribute("colspan");
|
||||
expect(".o_list_record_remove").toHaveCount(1);
|
||||
expect(".o_is_line_section .o_list_record_remove").toHaveCount(0);
|
||||
});
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { getFixture } from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
|
||||
QUnit.module("SectionOneToManyField", (hooks) => {
|
||||
let serverData;
|
||||
let target;
|
||||
|
||||
hooks.beforeEach(() => {
|
||||
target = getFixture();
|
||||
|
||||
serverData = {
|
||||
models: {
|
||||
partner: {
|
||||
fields: { lines: { type: "one2many", relation: "lines_sections" } },
|
||||
records: [
|
||||
{
|
||||
id: 1,
|
||||
lines: [1, 2],
|
||||
},
|
||||
],
|
||||
},
|
||||
lines_sections: {
|
||||
fields: {
|
||||
display_type: { type: "char" },
|
||||
title: { type: "char", string: "Title" },
|
||||
int: { type: "number", string: "integer" },
|
||||
},
|
||||
records: [
|
||||
{
|
||||
id: 1,
|
||||
display_type: "line_section",
|
||||
title: "firstSectionTitle",
|
||||
int: 4,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
display_type: false,
|
||||
title: "recordTitle",
|
||||
int: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
setupViewRegistries();
|
||||
});
|
||||
|
||||
QUnit.test("basic rendering", async (assert) => {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="lines" widget="section_one2many">
|
||||
<tree>
|
||||
<field name="display_type" invisible="1" />
|
||||
<field name="title" />
|
||||
<field name="int" />
|
||||
</tree>
|
||||
</field>
|
||||
</form>
|
||||
`,
|
||||
});
|
||||
assert.containsOnce(target, ".o_field_x2many .o_list_renderer table.o_section_list_view");
|
||||
assert.containsN(target, ".o_data_row", 2);
|
||||
const rows = target.querySelectorAll(".o_data_row");
|
||||
assert.hasClass(rows[0], "o_is_line_section fw-bold");
|
||||
assert.doesNotHaveClass(rows[1], "o_is_line_section fw-bold");
|
||||
assert.strictEqual(rows[0].textContent, "firstSectionTitle");
|
||||
assert.strictEqual(rows[1].textContent, "recordTitle5");
|
||||
assert.strictEqual(rows[0].querySelector("td[name=title]").getAttribute("colspan"), "3");
|
||||
assert.strictEqual(rows[1].querySelector("td[name=title]").getAttribute("colspan"), null);
|
||||
assert.containsOnce(target, ".o_list_record_remove");
|
||||
assert.containsNone(target, ".o_is_line_section .o_list_record_remove");
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class ResourceResource extends models.ServerModel {
|
||||
_name = "resource.resource";
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import { fields, models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class ResourceTask extends models.Model {
|
||||
_name = "resource.task";
|
||||
|
||||
display_name = fields.Char({ string: "Name" });
|
||||
resource_ids = fields.Many2many({ string: "Resources", relation: "resource.resource" });
|
||||
resource_id = fields.Many2one({ string: "Resource", relation: "resource.resource"});
|
||||
resource_type = fields.Char({ string: "Resource Type" });
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { ResourceTask } from "./mock_server/mock_models/resource_task";
|
||||
import { ResourceResource } from "./mock_server/mock_models/resource_resource";
|
||||
import { defineModels } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export const resourceModels = {
|
||||
ResourceTask,
|
||||
ResourceResource,
|
||||
};
|
||||
|
||||
export function defineResourceModels() {
|
||||
return defineModels(resourceModels);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue