视图
视图部件
视图部件在Odoo的注册分类为view_widgets。
列表视图
WC的列表视图(ListView)指的是视图(view)、控制器(controller)和渲染器(Renderer)三个部分。下面我们来逐一看以下这三个部分。
渲染器(Renderer)
我们来看渲染器的定义:
export class ListRenderer extends Component {
static template = "web.ListRenderer";
static rowsTemplate = "web.ListRenderer.Rows";
static recordRowTemplate = "web.ListRenderer.RecordRow";
static groupRowTemplate = "web.ListRenderer.GroupRow";
static useMagicColumnWidths = true;
static LONG_TOUCH_THRESHOLD = 400;
static components = { DropdownItem, Field, ViewButton, CheckBox, Dropdown, Pager, Widget };
static defaultProps = { hasSelectors: false, cycleOnTab: true };
static props = [
"activeActions?",
"list",
"archInfo",
"openRecord",
"onAdd?",
"cycleOnTab?",
"allowSelectors?",
"editable?",
"onOpenFormView?",
"hasOpenFormViewButton?",
"noContentHelp?",
"nestedKeyOptionalFieldsData?",
"optionalActiveFields?",
];
...
}
从定义我们可以看到列表视图被分成了4个视图模板来分别渲染。整体部件使用的是ListRenderer视图,其中的每一列使用的是Rows模板,有数据的行使用的是RecordRow, 分组视图使用的是GroupRow。
启动方法
接下来我们看以下启动方法(Setup):
setup() {
this.uiService = useService("ui");
this.notificationService = useService("notification");
const key = this.createViewKey();
this.keyOptionalFields = `optional_fields,${key}`;
this.keyDebugOpenView = `debug_open_view,${key}`;
this.cellClassByColumn = {};
this.groupByButtons = this.props.archInfo.groupBy.buttons;
useExternalListener(document, "click", this.onGlobalClick.bind(this));
this.tableRef = useRef("table");
this.longTouchTimer = null;
this.touchStartMs = 0;
...
}
方法很长,这里我们逐一来看。
this.creates = this.props.archInfo.creates.length
? this.props.archInfo.creates
: [{ type: "create", string: _t("Add a line") }];
this.cellToFocus = null;
this.activeRowId = null;
onMounted(async () => {
// Due to the way elements are mounted in the DOM by Owl (bottom-to-top),
// we need to wait the next micro task tick to set the activeElement.
await Promise.resolve();
this.activeElement = this.uiService.activeElement;
});
onWillPatch(() => {
const activeRow = document.activeElement.closest(".o_data_row.o_selected_row");
this.activeRowId = activeRow ? activeRow.dataset.id : null;
});
this.optionalActiveFields = this.props.optionalActiveFields || {};
this.allColumns = [];
this.columns = [];
onWillRender(() => {
this.allColumns = this.processAllColumn(this.props.archInfo.columns, this.props.list);
Object.assign(this.optionalActiveFields, this.computeOptionalActiveFields());
this.debugOpenView = exprToBoolean(browser.localStorage.getItem(this.keyDebugOpenView));
this.columns = this.getActiveColumns(this.props.list);
this.withHandleColumn = this.columns.some((col) => col.widget === "handle");
});
creates
我们在第二部分讲过列表视图中可以添加Add按钮,现在我们来看一下具体的实现。首先我们来看视图定义:
<t t-foreach="creates" t-as="create" t-key="create_index">
<a
t-if="create.type === 'create'"
href="#"
role="button"
t-att-class="create_index !== 0 ? 'ml16' : ''"
t-att-tabindex="props.list.editedRecord ? '-1' : '0'"
t-on-click.stop.prevent="() => this.add({ context: create.context })"
>
<t t-esc="create.string"/>
</a>
<ViewButton
t-if="create.type === 'button'"
className="`${create.className} ${create_index !== 0 ? 'ml16' : ''}`"
clickParams="create.clickParams"
icon="create.icon"
record="props.list"
string="create.string"
title="create.title"
tabindex="props.list.editedRecord ? '-1' : '0'"
/>
</t>
xml部分的代码印证我们之前的结论,列表控制器中只能添加两种类型的按钮。接下来我们看js代码部分:
this.creates = this.props.archInfo.creates.length
? this.props.archInfo.creates
: [{ type: "create", string: _t("Add a line") }];
this.creates属性用来存储列表视图中的Add系列按钮。如果视图中的creates按钮为空,那么给一个默认类型为create的Add a line的按钮。xml代码中指明了单击事件的处理方法add:
add(params) {
if (this.canCreate) {
this.props.onAdd(params);
}
}
先检查当前列表是否能够创建新记录,如果可以,则使用props属性的onAdd方法来添加一条新记录。params从定义的context中而来,用户可以指定不同的默认值,也可以自定义自己的实现。