odoo17核心概念action3——action_service

这是action系列的第三篇文章
action_service 是action的核心。
整个文件接近1500行代码,其中关键的两个函数是:

        doAction,
        doActionButton,

1、doAction

注释中写的很清楚:
doAction请求的主要入口点,加载一个Action并执行它。
根据不同的action类型调用不同的函数处理。


    // ---------------------------------------------------------------------------
    // public API
    // ---------------------------------------------------------------------------

    /**
     * Main entry point of a 'doAction' request. Loads the action and executes it.
     *
     * @param {ActionRequest} actionRequest
     * @param {ActionOptions} options
     * @returns {Promise<number | undefined | void>}
     */
    async function doAction(actionRequest, options = {}) {
        const actionProm = _loadAction(actionRequest, options.additionalContext);
        let action = await keepLast.add(actionProm);
        action = _preprocessAction(action, options.additionalContext);
        options.clearBreadcrumbs = action.target === "main" || options.clearBreadcrumbs;
        switch (action.type) {
            case "ir.actions.act_url":
                return _executeActURLAction(action, options);
            case "ir.actions.act_window":
                if (action.target !== "new") {
                    const canProceed = await clearUncommittedChanges(env);
                    if (!canProceed) {
                        return new Promise(() => {});
                    }
                }
                return _executeActWindowAction(action, options);
            case "ir.actions.act_window_close":
                return _executeCloseAction({ onClose: options.onClose, onCloseInfo: action.infos });
            case "ir.actions.client":
                return _executeClientAction(action, options);
            case "ir.actions.report":
                return _executeReportAction(action, options);
            case "ir.actions.server":
                return _executeServerAction(action, options);
            default: {
                const handler = actionHandlersRegistry.get(action.type, null);
                if (handler !== null) {
                    return handler({ env, action, options });
                }
                throw new Error(
                    `The ActionManager service can't handle actions of type ${action.type}`
                );
            }
        }
    }

我们看一下最常见的ir.actions.act_window

 async function _executeActWindowAction(action, options) {
        const views = [];
        debugger
        for (const [, type] of action.views) {
            if (type !== "search" && viewRegistry.contains(type)) {
                views.push(viewRegistry.get(type));
            }
        }
        if (!views.length) {
            throw new Error(`No view found for act_window action ${action.id}`);
        }

        let view = options.viewType && views.find((v) => v.type === options.viewType);
        let lazyView;

        if (view && !view.multiRecord) {
            lazyView = views[0].multiRecord ? views[0] : undefined;
        } else if (!view) {
            view = views[0];
        }

        if (env.isSmall) {
            view = _findView(views, view.multiRecord, action.mobile_view_mode) || view;
            if (lazyView) {
                lazyView =
                    _findView(views, lazyView.multiRecord, action.mobile_view_mode) || lazyView;
            }
        }

        const controller = {
            jsId: `controller_${++id}`,
            Component: View,
            action,
            view,
            views,
            ..._getViewInfo(view, action, views, options.props),
        };
        action.controllers[view.type] = controller;

        const updateUIOptions = {
            clearBreadcrumbs: options.clearBreadcrumbs,
            onClose: options.onClose,
            stackPosition: options.stackPosition,
            onActionReady: options.onActionReady,
            noEmptyTransition: options.noEmptyTransition,
        };

        if (lazyView) {
            updateUIOptions.lazyController = {
                jsId: `controller_${++id}`,
                Component: View,
                action,
                view: lazyView,
                views,
                ..._getViewInfo(lazyView, action, views),
            };
        }

        return _updateUI(controller, updateUIOptions);
    }

这个函数组装了一个controller

        const controller = {
            jsId: `controller_${++id}`,
            Component: View,
            action,
            view,
            views,
            ..._getViewInfo(view, action, views, options.props),
        };

最后将controller 传入_updateUI 函数 来更新界面。
而_updateUI的作用在于经过一些列处理后调用了trigger

 env.bus.trigger("ACTION_MANAGER:UPDATE", controller.__info__);

前文有说明,action_container会监听这个消息并且调用render函数刷新页面。

注意最后的default:

 const handler = actionHandlersRegistry.get(action.type, null);
                if (handler !== null) {
                    return handler({ env, action, options });
                }
                throw new Error(
                    `The ActionManager service can't handle actions of type ${action.type}`
                );

这里说明,odoo支持用户扩展自己的动作类型,并且将动作类型的处理函数注册到注册表中,这里就可以调用了。

有哪些场景需要扩展action的类型呢?

2、doActionButton

 /**
     * Executes an action on top of the current one (typically, when a button in a
     * view is clicked). The button may be of type 'object' (call a given method
     * of a given model) or 'action' (execute a given action). Alternatively, the
     * button may have the attribute 'special', and in this case an
     * 'ir.actions.act_window_close' is executed.
     *
     * @param {DoActionButtonParams} params
     * @returns {Promise<void>}
     */
    async function doActionButton(params) {
        // determine the action to execute according to the params
        let action;
        const context = makeContext([params.context, params.buttonContext]);
        if (params.special) {
            action = { type: "ir.actions.act_window_close", infos: { special: true } };
        } else if (params.type === "object") {
            // call a Python Object method, which may return an action to execute
            let args = params.resId ? [[params.resId]] : [params.resIds];
            if (params.args) {
                let additionalArgs;
                try {
                    // warning: quotes and double quotes problem due to json and xml clash
                    // maybe we should force escaping in xml or do a better parse of the args array
                    additionalArgs = JSON.parse(params.args.replace(/'/g, '"'));
                } catch {
                    browser.console.error("Could not JSON.parse arguments", params.args);
                }
                args = args.concat(additionalArgs);
            }
            const callProm = env.services.rpc("/web/dataset/call_button", {
                args,
                kwargs: { context },
                method: params.name,
                model: params.resModel,
            });
            action = await keepLast.add(callProm);
            action =
                action && typeof action === "object"
                    ? action
                    : { type: "ir.actions.act_window_close" };
            if (action.help) {
                action.help = markup(action.help);
            }
        } else if (params.type === "action") {
            // execute a given action, so load it first
            context.active_id = params.resId || null;
            context.active_ids = params.resIds;
            context.active_model = params.resModel;
            action = await keepLast.add(_loadAction(params.name, context));
        } else {
            throw new InvalidButtonParamsError("Missing type for doActionButton request");
        }
        // filter out context keys that are specific to the current action, because:
        //  - wrong default_* and search_default_* values won't give the expected result
        //  - wrong group_by values will fail and forbid rendering of the destination view
        const currentCtx = {};
        for (const key in params.context) {
            if (key.match(CTX_KEY_REGEX) === null) {
                currentCtx[key] = params.context[key];
            }
        }
        const activeCtx = { active_model: params.resModel };
        if (params.resId) {
            activeCtx.active_id = params.resId;
            activeCtx.active_ids = [params.resId];
        }
        action.context = makeContext([currentCtx, params.buttonContext, activeCtx, action.context]);
        // in case an effect is returned from python and there is already an effect
        // attribute on the button, the priority is given to the button attribute
        const effect = params.effect ? evaluateExpr(params.effect) : action.effect;
        const options = { onClose: params.onClose };
        await doAction(action, options);
        if (params.close) {
            await _executeCloseAction();
        }
        if (effect) {
            env.services.effect.add(effect);
        }
    }

1、当点击一个按钮的时候会调用这段代码,如果按钮有special属性,那么会执行ir.actions.act_window_close,也就是关闭当前窗口。
2、按钮有两张类型 object 和 action
3、如果是object,会执行调用rpc,调用后端代码
4、最终都是调用doAction函数执行的动作。

更具体的分析留到以后,这里只是简单的分析框架。

吐槽一下,在这个文件里发现一处有瑕疵的代码:

const DIALOG_SIZES = {
    "extra-large": "xl",
    large: "lg",
    medium: "md",
    small: "sm",
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值