OCA WEB REFRESH 插件讲解

Web Refresher (微信masterjmz)

此模块会在分页处添加一个刷新按扭,效果如下

在一些场景下面特别有用,实际上还可以增加定时刷新的功能,也就是在分页上还可以做定时刷新等,本质都一样。下面我们来看具体代码,首先是修改界面

<?xml version="1.0" encoding="UTF-8" ?>
<!-- Copyright 2022 Tecnativa - Alexandre Díaz
     Copyright 2023 Taras Shabaranskyi
     License AGPL-3.0 or later (<http://www.gnu.org/licenses/agpl>). -->
<template>
    <t
        t-name="web_refresher.ControlPanel"
        t-inherit="web.ControlPanel"
        t-inherit-mode="extension"
    >
        <xpath expr="//div[hasclass('o_cp_pager')]" position="before">
            <div class="oe_cp_refresher" role="search" t-ref="refresher">
                <Refresher t-props="refresherProps" />
            </div>
        </xpath>
    </t>
</template>

在pager前面添加了一个刷新按扭,其次是本身组件的实现

/** @odoo-module **/
/* Copyright 2022 Tecnativa - Alexandre D. Díaz
 * Copyright 2022 Tecnativa - Carlos Roca
 * Copyright 2023 Taras Shabaranskyi
 * License AGPL-3.0 or later (<http://www.gnu.org/licenses/agpl>). */

import {Component} from "@odoo/owl";
import {useDebounced} from "@web/core/utils/timing";
import {useService} from "@web/core/utils/hooks";

export function useRefreshAnimation(timeout) {
    const refreshClass = "o_content__refresh";
    let timeoutId = null;

    /**
     * @returns {DOMTokenList|null}
     */
    function contentClassList() {
        const content = document.querySelector(".o_content");
        return content ? content.classList : null;
    }

    function clearAnimationTimeout() {
        if (timeoutId) {
            clearTimeout(timeoutId);
        }
        timeoutId = null;
    }

    function animate() {
        clearAnimationTimeout();
        contentClassList().add(refreshClass);
        timeoutId = setTimeout(() => {
            contentClassList().remove(refreshClass);
            clearAnimationTimeout();
        }, timeout);
    }

    return animate;
}

export class Refresher extends Component {
    setup() {
        super.setup();
        this.action = useService("action");
        this.refreshAnimation = useRefreshAnimation(1000);
        this.onClickRefresh = useDebounced(this.onClickRefresh, 200);
    }

    /**
     * @returns {Boolean}
     * @private
     */
    _searchModelRefresh() {
        const {searchModel} = this.props;
        if (searchModel && typeof searchModel.search === "function") {
            searchModel.search();
            return true;
        }
        return false;
    }

    /**
     * @returns {Promise<Boolean>}
     * @private
     */
    async _pagerRefresh() {
        const pagerProps = this.props.pagerProps;
        if (pagerProps && typeof pagerProps.onUpdate === "function") {
            const {limit, offset} = pagerProps;
            await pagerProps.onUpdate({offset, limit});
            return true;
        }
        return false;
    }

    /**
     * @returns {Promise<Boolean>}
     */
    async refresh() {
        let updated = this._searchModelRefresh();
        if (!updated) {
            updated = await this._pagerRefresh();
        }
        return updated;
    }

    /**
     * Function to refresh the views that has not the props
     * required by the refresher, like ir.actions.report or
     * ir.actions.client.
     */
    async refreshReport() {
        const viewAction = this.action.currentController.action;
        const options = {};
        if (this.env.config.breadcrumbs.length > 1) {
            const breadcrumb = this.env.config.breadcrumbs.slice(-1);
            await this.action.restore(breadcrumb.jsId);
        } else {
            options.clearBreadcrumbs = true;
        }
        this.action.doAction(viewAction, options);
    }

    async onClickRefresh() {
        const {searchModel, pagerProps} = this.props;
        if (!searchModel && !pagerProps) {
            return this.refreshReport();
        }
        const updated = await this.refresh();
        if (updated) {
            this.refreshAnimation();
        }
    }
}

Object.assign(Refresher, {
    template: "web_refresher.Button",
    props: {
        searchModel: {type: Object, optional: true},
        pagerProps: {type: Object, optional: true},
    },
});

代码中实际上还添加了一个动画,这个动画使用的是定时方式,实际上可以在加载结束以后去除这个动化应当会更好一些,刷新分成了三种情况

1、searchmodel.

2、pager.

3、report。

实际上如果是自定义的client最好是给个消息让自定义的client去进行处理。代码如 、

    async onClickRefresh() {
        const {searchModel, pagerProps} = this.props;
        if (!searchModel && !pagerProps) {
            return this.refreshReport();
        }
        const updated = await this.refresh();
        if (updated) {
            this.refreshAnimation();
        }
    }

如果没有searchmodel和pagerprops的时候则判断为report,这里或许有点武断,这个最好让页面自身去判断,同时也还存在自定义页面的情况, 如果不是report则进行刷新,之后如果进行了更新则调用动画,动化比较简单,这里或许到三种情况写在一起的话会更好理解一点,不过写代码不能太纠结。 动画代码如下,实际上就是添加了一个动画的class。

export function useRefreshAnimation(timeout) {
    const refreshClass = "o_content__refresh";
    let timeoutId = null;

    /**
     * @returns {DOMTokenList|null}
     */
    function contentClassList() {
        const content = document.querySelector(".o_content");
        return content ? content.classList : null;
    }

    function clearAnimationTimeout() {
        if (timeoutId) {
            clearTimeout(timeoutId);
        }
        timeoutId = null;
    }

    function animate() {
        clearAnimationTimeout();
        contentClassList().add(refreshClass);
        timeoutId = setTimeout(() => {
            contentClassList().remove(refreshClass);
            clearAnimationTimeout();
        }, timeout);
    }

    return animate;
}

在content上加了一个动画class, 然后在定时结束的时候去除掉。