示例#1
0
odoo.define('board.dashboard', function (require) {
"use strict";

var Context = require('web.Context');
var core = require('web.core');
var dataManager = require('web.data_manager');
var Dialog = require('web.Dialog');
var Domain = require('web.Domain');
var FormController = require('web.FormController');
var FormRenderer = require('web.FormRenderer');
var FormView = require('web.FormView');
var viewRegistry = require('web.view_registry');

var _t = core._t;
var QWeb = core.qweb;

FormView.include({
    /**
     * @override
     */
    init: function (viewInfo) {
        this._super.apply(this, arguments);
        this.controllerParams.viewID = viewInfo.view_id;
    },
});

FormController.include({
    custom_events: _.extend({}, FormController.prototype.custom_events, {
        change_layout: '_onChangeLayout',
        enable_dashboard: '_onEnableDashboard',
        save_dashboard: '_saveDashboard',
        switch_view: '_onSwitchView',
    }),
    init: function (parent, model, renderer, params) {
        this._super.apply(this, arguments);
        this.viewID = params.viewID;
    },

    //--------------------------------------------------------------------------
    // Public
    //--------------------------------------------------------------------------

    /**
     * @override
     */
    getTitle: function () {
        if (this.inDashboard) {
            return _t("My Dashboard");
        }
        return this._super.apply(this, arguments);
    },

    //--------------------------------------------------------------------------
    // Private
    //--------------------------------------------------------------------------

    /**
     * Actually save a dashboard
     *
     * @returns {Deferred}
     */
    _saveDashboard: function () {
        var board = this.renderer.getBoard();
        var arch = QWeb.render('DashBoard.xml', _.extend({}, board));
        return this._rpc({
                route: '/web/view/add_custom',
                params: {
                    view_id: this.viewID,
                    arch: arch,
                }
            }).then(dataManager.invalidate.bind(dataManager));
    },

    //--------------------------------------------------------------------------
    // Handlers
    //--------------------------------------------------------------------------

    /**
     * @private
     * @param {OdooEvent} event
     */
    _onChangeLayout: function (event) {
        var self = this;
        var dialog = new Dialog(this, {
            title: _t("Edit Layout"),
            $content: QWeb.render('DashBoard.layouts', _.clone(event.data))
        });
        dialog.opened().then(function () {
            dialog.$('li').click(function () {
                var layout = $(this).attr('data-layout');
                self.renderer.changeLayout(layout);
                self._saveDashboard();
                dialog.close();
            });
        });
        dialog.open();
    },

    /**
     * @private
     */
    _onEnableDashboard: function () {
        this.inDashboard = true;
    },

    /**
     * We need to intercept switch_view event coming from sub views, because
     * there is no view manager doing the job.  Also, we don't actually want to
     * switch view in dashboard, we want to do a do_action (which will open the
     * record in a different breadcrumb)
     *
     * @private
     * @param {OdooEvent} event
     */
    _onSwitchView: function (event) {
        event.stopPropagation();
        this.do_action({
            type: 'ir.actions.act_window',
            res_model: event.data.model,
            views: [[false, 'form']],
            res_id: event.data.res_id,
        });

    },

});

FormRenderer.include({
    events: _.extend({}, FormRenderer.prototype.events, {
        'click .oe_dashboard_column .oe_fold': '_onFoldClick',
        'click .oe_dashboard_link_change_layout': '_onChangeLayout',
        'click .oe_dashboard_column .oe_close': '_onCloseAction',
    }),

    /**
     * @override
     */
    init: function (parent, state, params) {
        this._super.apply(this, arguments);
        this.noContentHelp = params.noContentHelp;
        this.actionsDescr = {};
    },

    //--------------------------------------------------------------------------
    // Public
    //--------------------------------------------------------------------------

    /**
     * @param {string} layout
     */
    changeLayout: function (layout) {
        var $dashboard = this.$('.oe_dashboard');
        var current_layout = $dashboard.attr('data-layout');
        if (current_layout !== layout) {
            var clayout = current_layout.split('-').length,
                nlayout = layout.split('-').length,
                column_diff = clayout - nlayout;
            if (column_diff > 0) {
                var $last_column = $();
                $dashboard.find('.oe_dashboard_column').each(function (k, v) {
                    if (k >= nlayout) {
                        $(v).find('.oe_action').appendTo($last_column);
                    } else {
                        $last_column = $(v);
                    }
                });
            }
            $dashboard.toggleClass('oe_dashboard_layout_' + current_layout + ' oe_dashboard_layout_' + layout);
            $dashboard.attr('data-layout', layout);
        }
    },

    /**
     * Returns a representation of the current dashboard
     *
     * @returns {Object}
     */
    getBoard: function () {
        var self = this;
        var board = {
            form_title : this.arch.attrs.string,
            style : this.$('.oe_dashboard').attr('data-layout'),
            columns : [],
        };
        this.$('.oe_dashboard_column').each(function () {
            var actions = [];
            $(this).find('.oe_action').each(function () {
                var actionID = $(this).attr('data-id');
                var newAttrs = _.clone(self.actionsDescr[actionID]);

                /* prepare attributes as they should be saved */
                if (newAttrs.modifiers) {
                    newAttrs.modifiers = JSON.stringify(newAttrs.modifiers);
                }
                actions.push(newAttrs);
            });
            board.columns.push(actions);
        });
        return board;
    },

    //--------------------------------------------------------------------------
    // Private
    //--------------------------------------------------------------------------

    /**
     * @private
     * @param {Object} params
     * @param {jQueryElement} params.$node
     * @param {integer} params.actionID
     * @param {Object} params.context
     * @param {any[]} params.domain
     * @param {string} params.viewType
     * @returns {Deferred}
     */
    _createController: function (params) {
        var self = this;
        var context = params.context.eval();
        return this._rpc({
                route: '/web/action/load',
                params: {action_id: params.actionID}
            })
            .then(function (action) {
                if (!action) {
                    // the action does not exist anymore
                    return $.when();
                }
                var view = _.find(action.views, function (descr) {
                    return descr[1] === params.viewType;
                });
                return self.loadViews(action.res_model, context, [view])
                           .then(function (viewsInfo) {
                    var viewInfo = viewsInfo[params.viewType];
                    var View = viewRegistry.get(params.viewType);
                    var view = new View(viewInfo, {
                        action: action,
                        context: context,
                        domain: params.domain,
                        groupBy: context.group_by,
                        modelName: action.res_model,
                        hasSelectors: false,
                    });
                    return view.getController(self).then(function (controller) {
                        return controller.appendTo(params.$node);
                    });
                });
            });
    },
    /**
     * @private
     * @param {Object} node
     * @returns {jQueryElement}
     */
    _renderTagBoard: function (node) {
        var self = this;
        // we add the o_dashboard class to the renderer's $el. This means that
        // this function has a side effect.  This is ok because we assume that
        // once we have a '<board>' tag, we are in a special dashboard mode.
        this.$el.addClass('o_dashboard');
        this.trigger_up('enable_dashboard');

        var hasAction = _.detect(node.children, function (column) {
            return _.detect(column.children,function (element){
                return element.tag === "action"? element: false;
            });
        });
        if (!hasAction) {
            return $('<div class="oe_view_nocontent">')
                .append($('<div>').html(this.noContentHelp || " "));
        }
        // We should start with three columns available
        node = $.extend(true, {}, node);

        // no idea why master works without this, but whatever
        if (!('layout' in node.attrs)) {
            node.attrs.layout = node.attrs.style;
        }
        for (var i = node.children.length; i < 3; i++) {
            node.children.push({
                tag: 'column',
                attrs: {},
                children: []
            });
        }

        // register actions, alongside a generated unique ID
        _.each(node.children, function (column, column_index) {
            _.each(column.children, function (action, action_index) {
                action.attrs.id = 'action_' + column_index + '_' + action_index;
                self.actionsDescr[action.attrs.id] = action.attrs;
            });
        });

        var $html = $('<div>').append($(QWeb.render('DashBoard', {node: node})));

        // render each view
        _.each(this.actionsDescr, function (action) {
            self.defs.push(self._createController({
                $node: $html.find('.oe_action[data-id=' + action.id + '] .oe_content'),
                actionID: _.str.toNumber(action.name),
                context: new Context(action.context),
                domain: Domain.prototype.stringToArray(action.domain, {}),
                viewType: action.view_mode,
            }));
        });
        $html.find('.oe_dashboard_column').sortable({
            connectWith: '.oe_dashboard_column',
            handle: '.oe_header',
            scroll: false
        }).bind('sortstop', function () {
            self.trigger_up('save_dashboard');
        });

        return $html;
    },

    //--------------------------------------------------------------------------
    // Handlers
    //--------------------------------------------------------------------------

    /**
     * @private
     */
    _onChangeLayout: function () {
        var currentLayout = this.$('.oe_dashboard').attr('data-layout');
        this.trigger_up('change_layout', {currentLayout: currentLayout});
    },
    /**
     * @private
     * @param {MouseEvent} event
     */
    _onCloseAction: function (event) {
        var self = this;
        var $container = $(event.currentTarget).parents('.oe_action:first');
        Dialog.confirm(this, (_t("Are you sure you want to remove this item?")), {
            confirm_callback: function () {
                $container.remove();
                self.trigger_up('save_dashboard');
            },
        });
    },
    /**
     * @private
     * @param {MouseEvent} event
     */
    _onFoldClick: function (event) {
        var $e = $(event.currentTarget);
        var $action = $e.closest('.oe_action');
        var id = $action.data('id');
        var actionAttrs = this.actionsDescr[id];

        if ($e.is('.oe_minimize')) {
            actionAttrs.fold = '1';
        } else {
            delete(actionAttrs.fold);
        }
        $e.toggleClass('oe_minimize oe_maximize');
        $action.find('.oe_content').toggle();
        this.trigger_up('save_dashboard');
    },
});

});
示例#2
0
odoo.define('base.settings', function (require) {
"use strict";

var BasicModel = require('web.BasicModel');
var core = require('web.core');
var config = require('web.config');
var FormView = require('web.FormView');
var FormController = require('web.FormController');
var FormRenderer = require('web.FormRenderer');
var view_registry = require('web.view_registry');

var QWeb = core.qweb;

var BaseSettingRenderer = FormRenderer.extend({
    events: _.extend({}, FormRenderer.prototype.events, {
        'click .tab': '_onSettingTabClick',
        'keyup .searchInput': '_onKeyUpSearch',
    }),

    init: function () {
        this._super.apply(this, arguments);
        this.activeView = false;
        this.activeTab = false;
    },

    start: function () {
        this._super.apply(this, arguments);
        if (config.device.isMobile) {
            core.bus.on("DOM_updated", this, function () {
                this._moveToTab(this.currentIndex || this._currentAppIndex());
            });
        }
    },

    /**
     * @override
     */
    on_attach_callback: function () {
        this._super.apply(this, arguments);
        // set default focus on searchInput
        this.searchInput.focus();
    },

    /**
     * initialize modules list.
     * remove module that restricted in groups
     * data contains
     *  {
     *     key: moduel key
     *     string: moduel string
     *     imgurl: icon url
     *  }
     *
     * @private
     */
    _initModules: function () {
        var self = this;
        this.modules = [];
        _.each(this.$('.app_settings_block'), function (settingView, index) {
            var group = !$(settingView).hasClass('o_invisible_modifier');
            var isNotApp = $(settingView).hasClass('o_not_app');
            if(group && !isNotApp) {
                var data = $(settingView).data();
                data.string = $(settingView).attr('string') || data.string;
                self.modules.push({
                    key: data.key,
                    string: data.string,
                    imgurl: self._getAppIconUrl(data.key),
                });
            } else {
                $(settingView).remove();
            }
        });
    },
    /**
     * initialize searchtext variable
     * initialize jQuery search input element
     *
     * @private
     */
    _initSearch: function () {
        this.searchInput = this.$('.searchInput');
        if (this.searchText) {
            this.searchInput.val(this.searchText);
            this._onKeyUpSearch();
        } else {
            this.searchText = "";
        }
    },
    /**
     * In mobile view behaviour is like swipe content left / right and apps tab will be shown on the top.
     * This method will set the required properties (styling and css).
     *
     * @private
     * @param {int} currentTab
     */
    _activateSettingMobileTab: function (currentTab) {
        var self = this;
        var moveTo = currentTab;
        var next = moveTo + 1;
        var previous = moveTo - 1;

        this.$(".settings .app_settings_block").removeClass("previous next current before after");
        this.$(".settings_tab .tab").removeClass("previous next current before after");
        _.each(this.modules, function (module, index) {
            var tab = self.$(".tab[data-key='" + module.key + "']");
            var view = module.settingView;

            if (index === previous) {
                tab.addClass("previous");
                tab.css("margin-left", "0px");
                view.addClass("previous");
            } else if (index === next) {
                tab.addClass("next");
                tab.css("margin-left", "-" + tab.outerWidth() + "px");
                view.addClass("next");
            } else if (index < moveTo) {
                tab.addClass("before");
                tab.css("margin-left", "-" + tab.outerWidth() + "px");
                view.addClass("before");
            } else if (index === moveTo) {
                var marginLeft = tab.outerWidth() / 2;
                tab.css("margin-left", "-" + marginLeft + "px");
                tab.addClass("current");
                view.addClass("current");
            } else if (index > moveTo) {
                tab.addClass("after");
                tab.css("margin-left", "0");
                view.addClass("after");
            }
        });
    },
    /**
     * find current app index in modules
     *
     */
    _currentAppIndex: function () {
        var self = this;
        var index = _.findIndex(this.modules, function (module) {
            return module.key === self.activeSettingTab;
        });
        return index;
    },
    /**
     * Enables swipe navigation between settings pages
     *
     * @private
     */
    _enableSwipe: function () {
        var self = this;
        this.$('.settings').swipe({
            swipeLeft: function () {
                self._moveToTab(self.currentIndex + 1);
            },
            swipeRight: function () {
                self._moveToTab(self.currentIndex - 1);
            }
        });
    },
    /**
     *
     * @private
     * @param {string} module
     * @returns {string} icon url
     */
    _getAppIconUrl: function (module) {
        return module === "general_settings" ? "/base/static/description/settings.png" : "/"+module+"/static/description/icon.png";
    },
    /**
     *
     * @private
     * @param {string} imgurl
     * @param {string} string(moduel name)
     * @returns {object}
     */
    _getSearchHeader: function (imgurl, string) {
        return $(QWeb.render('BaseSetting.SearchHeader', {
            imgurl: imgurl,
            string: string
        }));
    },
    /**
     * add placeholder attr in input element
     * @override
     * @private
     * @param {jQueryElement} $el
     * @param {Object} node
     */
    _handleAttributes: function ($el, node) {
        this._super.apply(this, arguments);
        if (node.attrs.placeholder) {
            $el.attr('placeholder', node.attrs.placeholder);
        }
    },
    /**
     * move to selected setting
     *
     * @private
     * @param {int} index
     */
    _moveToTab: function (index) {
        this.currentIndex = !index || index === -1 ? 0 : (index === this.modules.length ? index - 1 : index);
        if (this.currentIndex !== -1) {
            if (this.activeView) {
                this.activeView.addClass("o_hidden");
            }
            if (this.activeTab) {
                this.activeTab.removeClass("selected");
            }
            var view = this.modules[this.currentIndex].settingView;
            var tab = this.$(".tab[data-key='" + this.modules[this.currentIndex].key + "']");
            view.removeClass("o_hidden");
            this.activeView = view;
            this.activeTab = tab;

            if (config.device.isMobile) {
                this._activateSettingMobileTab(this.currentIndex);
            } else {
                tab.addClass("selected");
            }
        }
    },

    _onSettingTabClick: function (event) {
        this.searchInput.focus();
        if (this.searchText.length > 0) {
            this.searchInput.val('');
            this.searchText = "";
            this._searchSetting();
        }
        var settingKey = this.$(event.currentTarget).data('key');
        this._moveToTab(_.findIndex(this.modules, function (m) {
            return m.key === settingKey;
        }));
    },

    _onKeyUpSearch: function (event) {
        this.searchText = this.searchInput.val();
        this.activeTab.removeClass('selected');
        if (config.device.isMobile) {
            this.$('.settings_tab').addClass('o_hidden');
            this.$('.settings').addClass('d-block');
        }
        this._searchSetting();
    },
    /**
     * reset setting view
     *
     * @private
     */
    _resetSearch: function () {
        this.searchInput.val("");
        _.each(this.modules, function (module) {
            module.settingView.addClass('o_hidden');
            module.settingView.find('.o_setting_box').removeClass('o_hidden');
            module.settingView.find('h2').removeClass('o_hidden');
            module.settingView.find('.settingSearchHeader').addClass('o_hidden');
            module.settingView.find('.o_settings_container').addClass('mt16');
        });
        this.activeTab.removeClass('o_hidden').addClass('selected');
        this.activeView.removeClass('o_hidden');
        if (config.device.isMobile) {
            this.$('.settings_tab').removeClass('o_hidden');
            this.$('.settings').removeClass('d-block');
        }
    },

    _render: function () {
        var res = this._super.apply(this, arguments);
        this._initModules();
        this._renderLeftPanel();
        this._initSearch();
        if (config.device.isMobile) {
            this._enableSwipe();
        }
        return res;
    },

    _renderLeftPanel: function () {
        var self = this;
        _.each(this.modules, function (module) {
            module.settingView = self.$('.app_settings_block[data-key="' + module.key + '"]');
            module.settingView.addClass("o_hidden");
            module.settingView.prepend(self._getSearchHeader(module.imgurl, module.string));
        });
        this._renderTabs();
        this._moveToTab(this.currentIndex || this._currentAppIndex());
    },

    _renderTabs: function () {
        var tabs = $(QWeb.render('BaseSetting.Tabs', {tabItems : this.modules}));
        tabs.appendTo(this.$(".settings_tab"));
    },
    /**
     * search setting in DOM
     *
     * @private
     */
    _searchSetting: function () {
        var self = this;
        this.count = 0;
        _.each(this.modules, function (module) {
            self.inVisibleCount = 0;
            module.settingView.find('.o_setting_box').addClass('o_hidden');
            module.settingView.find('h2').addClass('o_hidden');
            module.settingView.find('.settingSearchHeader').addClass('o_hidden');
            module.settingView.find('.o_settings_container').removeClass('mt16');
            var resultSetting = module.settingView.find("label:containsTextLike('" + self.searchText + "')");
            if (resultSetting.length > 0) {
                resultSetting.each(function () {
                    var settingBox = $(this).closest('.o_setting_box');
                    if (!settingBox.hasClass('o_invisible_modifier')) {
                        settingBox.removeClass('o_hidden');
                        $(this).html(self._wordHighlighter($(this).html(), self.searchText));
                    } else {
                        self.inVisibleCount++;
                    }
                });
                if (self.inVisibleCount !== resultSetting.length) {
                    module.settingView.find('.settingSearchHeader').removeClass('o_hidden');
                    module.settingView.removeClass('o_hidden');
                }
            } else {
                ++self.count;
            }
        });
        this.count === _.size(this.modules) ? this.$('.notFound').removeClass('o_hidden') : this.$('.notFound').addClass('o_hidden');
        if (this.searchText.length === 0) {
            this._resetSearch();
        }
    },
    /**
     * highlight search word
     *
     * @private
     * @param {string} text
     * @param {string} word
     */
    _wordHighlighter: function (text, word) {
        if (text.indexOf('highlighter') !== -1) {
            text = text.replace('<span class="highlighter">', "");
            text = text.replace("</span>", "");
        }
        var match = text.search(new RegExp(word, "i"));
        word = text.substring(match, match + word.length);
        var hilitedWord = "<span class='highlighter'>" + word + '</span>';
        return text.replace(word, hilitedWord);
    },
});

var BaseSettingController = FormController.extend({
    init: function () {
        this._super.apply(this, arguments);
        this.disableAutofocus = true;
        this.renderer.activeSettingTab = this.initialState.context.module;
    },
});

var BaseSettingsModel = BasicModel.extend({
    /**
     * @override
     */
    save: function (recordID) {
        var self = this;
        return this._super.apply(this, arguments).then(function (result) {
            // we remove here the res_id, because the record should still be
            // considered new.  We want the web client to always perform a
            // default_get to fetch the settings anew.
            delete self.localData[recordID].res_id;
            return result;
        });
    },
});

var BaseSettingView = FormView.extend({
    jsLibs: [],

    config: _.extend({}, FormView.prototype.config, {
        Model: BaseSettingsModel,
        Renderer: BaseSettingRenderer,
        Controller: BaseSettingController,
    }),

    /**
     * Overrides to lazy-load touchSwipe library in mobile.
     *
     * @override
    */
    init: function () {
        if (config.device.isMobile) {
            this.jsLibs.push('/web/static/lib/jquery.touchSwipe/jquery.touchSwipe.js');
        }
        this._super.apply(this, arguments);
    },
});

view_registry.add('base_settings', BaseSettingView);

return {
    Model: BaseSettingsModel,
    Renderer: BaseSettingRenderer,
    Controller: BaseSettingController,
};
});
示例#3
0
odoo.define('board.BoardView', function (require) {
"use strict";

var Context = require('web.Context');
var core = require('web.core');
var dataManager = require('web.data_manager');
var Dialog = require('web.Dialog');
var Domain = require('web.Domain');
var FormController = require('web.FormController');
var FormRenderer = require('web.FormRenderer');
var FormView = require('web.FormView');
var pyUtils = require('web.py_utils');
var session  = require('web.session');
var viewRegistry = require('web.view_registry');

var _t = core._t;
var _lt = core._lt;
var QWeb = core.qweb;

var BoardController = FormController.extend({
    custom_events: _.extend({}, FormController.prototype.custom_events, {
        change_layout: '_onChangeLayout',
        enable_dashboard: '_onEnableDashboard',
        save_dashboard: '_saveDashboard',
        switch_view: '_onSwitchView',
    }),

    /**
     * @override
     */
    init: function (parent, model, renderer, params) {
        this._super.apply(this, arguments);
        this.customViewID = params.customViewID;
    },

    //--------------------------------------------------------------------------
    // Public
    //--------------------------------------------------------------------------

    /**
     * @override
     */
    getTitle: function () {
        if (this.inDashboard) {
            return _t("My Dashboard");
        }
        return this._super.apply(this, arguments);
    },

    //--------------------------------------------------------------------------
    // Private
    //--------------------------------------------------------------------------

    /**
     * Actually save a dashboard
     *
     * @returns {Deferred}
     */
    _saveDashboard: function () {
        var board = this.renderer.getBoard();
        var arch = QWeb.render('DashBoard.xml', _.extend({}, board));
        return this._rpc({
                route: '/web/view/edit_custom',
                params: {
                    custom_id: this.customViewID,
                    arch: arch,
                }
            }).then(dataManager.invalidate.bind(dataManager));
    },

    //--------------------------------------------------------------------------
    // Handlers
    //--------------------------------------------------------------------------

    /**
     * @private
     * @param {OdooEvent} event
     */
    _onChangeLayout: function (event) {
        var self = this;
        var dialog = new Dialog(this, {
            title: _t("Edit Layout"),
            $content: QWeb.render('DashBoard.layouts', _.clone(event.data))
        });
        dialog.opened().then(function () {
            dialog.$('li').click(function () {
                var layout = $(this).attr('data-layout');
                self.renderer.changeLayout(layout);
                self._saveDashboard();
                dialog.close();
            });
        });
        dialog.open();
    },
    /**
     * @private
     */
    _onEnableDashboard: function () {
        this.inDashboard = true;
    },
    /**
     * We need to intercept switch_view event coming from sub views, because we
     * don't actually want to switch view in dashboard, we want to do a
     * do_action (which will open the record in a different breadcrumb).
     *
     * @private
     * @param {OdooEvent} event
     */
    _onSwitchView: function (event) {
        event.stopPropagation();
        this.do_action({
            type: 'ir.actions.act_window',
            res_model: event.data.model,
            views: [[event.data.formViewID || false, 'form']],
            res_id: event.data.res_id,
        });
    },
});

var BoardRenderer = FormRenderer.extend({
    custom_events: _.extend({}, FormRenderer.prototype.custom_events, {
        do_action: '_onDoAction',
        env_updated: '_onEnvUpdated',
        update_filters: '_onUpdateFilters',
        switch_view: '_onSwitchView',
    }),
    events: _.extend({}, FormRenderer.prototype.events, {
        'click .oe_dashboard_column .oe_fold': '_onFoldClick',
        'click .oe_dashboard_link_change_layout': '_onChangeLayout',
        'click .oe_dashboard_column .oe_close': '_onCloseAction',
    }),

    /**
     * @override
     */
    init: function (parent, state, params) {
        this._super.apply(this, arguments);
        this.noContentHelp = params.noContentHelp;
        this.actionsDescr = {};
        this._boardSubcontrollers = []; // for board: controllers of subviews
        this._boardFormViewIDs = {}; // for board: mapping subview controller to form view id
    },
    /**
     * Call `on_attach_callback` for each subview
     *
     * @override
     */
    on_attach_callback: function () {
        _.each(this._boardSubcontrollers, function (controller) {
            if ('on_attach_callback' in controller) {
                controller.on_attach_callback();
            }
        });
    },
    /**
     * Call `on_detach_callback` for each subview
     *
     * @override
     */
    on_detach_callback: function () {
        _.each(this._boardSubcontrollers, function (controller) {
            if ('on_detach_callback' in controller) {
                controller.on_detach_callback();
            }
        });
    },

    //--------------------------------------------------------------------------
    // Public
    //--------------------------------------------------------------------------

    /**
     * @param {string} layout
     */
    changeLayout: function (layout) {
        var $dashboard = this.$('.oe_dashboard');
        var current_layout = $dashboard.attr('data-layout');
        if (current_layout !== layout) {
            var clayout = current_layout.split('-').length,
                nlayout = layout.split('-').length,
                column_diff = clayout - nlayout;
            if (column_diff > 0) {
                var $last_column = $();
                $dashboard.find('.oe_dashboard_column').each(function (k, v) {
                    if (k >= nlayout) {
                        $(v).find('.oe_action').appendTo($last_column);
                    } else {
                        $last_column = $(v);
                    }
                });
            }
            $dashboard.toggleClass('oe_dashboard_layout_' + current_layout + ' oe_dashboard_layout_' + layout);
            $dashboard.attr('data-layout', layout);
        }
    },
    /**
     * Returns a representation of the current dashboard
     *
     * @returns {Object}
     */
    getBoard: function () {
        var self = this;
        var board = {
            form_title : this.arch.attrs.string,
            style : this.$('.oe_dashboard').attr('data-layout'),
            columns : [],
        };
        this.$('.oe_dashboard_column').each(function () {
            var actions = [];
            $(this).find('.oe_action').each(function () {
                var actionID = $(this).attr('data-id');
                var newAttrs = _.clone(self.actionsDescr[actionID]);

                /* prepare attributes as they should be saved */
                if (newAttrs.modifiers) {
                    newAttrs.modifiers = JSON.stringify(newAttrs.modifiers);
                }
                actions.push(newAttrs);
            });
            board.columns.push(actions);
        });
        return board;
    },

    //--------------------------------------------------------------------------
    // Private
    //--------------------------------------------------------------------------

    /**
     * @private
     * @param {Object} params
     * @param {jQueryElement} params.$node
     * @param {integer} params.actionID
     * @param {Object} params.context
     * @param {any[]} params.domain
     * @param {string} params.viewType
     * @returns {Deferred}
     */
    _createController: function (params) {
        var self = this;
        return this._rpc({
                route: '/web/action/load',
                params: {action_id: params.actionID}
            })
            .then(function (action) {
                if (!action) {
                    // the action does not exist anymore
                    return $.when();
                }
                var evalContext = new Context(params.context).eval();
                if (evalContext.group_by && evalContext.group_by.length === 0) {
                    delete evalContext.group_by;
                }
                // tz and lang are saved in the custom view
                // override the language to take the current one
                var rawContext = new Context(action.context, evalContext, {lang: session.user_context.lang});
                var context = pyUtils.eval('context', rawContext, evalContext);
                var domain = params.domain || pyUtils.eval('domain', action.domain || '[]', action.context);
                var viewType = params.viewType || action.views[0][1];
                var view = _.find(action.views, function (descr) {
                    return descr[1] === viewType;
                }) || [false, viewType];
                return self.loadViews(action.res_model, context, [view])
                           .then(function (viewsInfo) {
                    var viewInfo = viewsInfo[viewType];
                    var View = viewRegistry.get(viewType);
                    var view = new View(viewInfo, {
                        action: action,
                        context: context,
                        domain: domain,
                        groupBy: context.group_by || [],
                        modelName: action.res_model,
                        hasSelectors: false,
                    });
                    return view.getController(self).then(function (controller) {
                        self._boardFormViewIDs[controller.handle] = _.first(
                            _.find(action.views, function (descr) {
                                return descr[1] === 'form';
                            })
                        );
                        self._boardSubcontrollers.push(controller);
                        return controller.appendTo(params.$node);
                    });
                });
            });
    },
    /**
     * @private
     * @param {Object} node
     * @returns {jQueryElement}
     */
    _renderTagBoard: function (node) {
        var self = this;
        // we add the o_dashboard class to the renderer's $el. This means that
        // this function has a side effect.  This is ok because we assume that
        // once we have a '<board>' tag, we are in a special dashboard mode.
        this.$el.addClass('o_dashboard');
        this.trigger_up('enable_dashboard');

        var hasAction = _.detect(node.children, function (column) {
            return _.detect(column.children,function (element){
                return element.tag === "action"? element: false;
            });
        });
        if (!hasAction) {
            return $(QWeb.render('DashBoard.NoContent'));
        }

        // We should start with three columns available
        node = $.extend(true, {}, node);

        // no idea why master works without this, but whatever
        if (!('layout' in node.attrs)) {
            node.attrs.layout = node.attrs.style;
        }
        for (var i = node.children.length; i < 3; i++) {
            node.children.push({
                tag: 'column',
                attrs: {},
                children: []
            });
        }

        // register actions, alongside a generated unique ID
        _.each(node.children, function (column, column_index) {
            _.each(column.children, function (action, action_index) {
                action.attrs.id = 'action_' + column_index + '_' + action_index;
                self.actionsDescr[action.attrs.id] = action.attrs;
            });
        });

        var $html = $('<div>').append($(QWeb.render('DashBoard', {node: node})));

        // render each view
        _.each(this.actionsDescr, function (action) {
            self.defs.push(self._createController({
                $node: $html.find('.oe_action[data-id=' + action.id + '] .oe_content'),
                actionID: _.str.toNumber(action.name),
                context: action.context,
                domain: Domain.prototype.stringToArray(action.domain, {}),
                viewType: action.view_mode,
            }));
        });
        $html.find('.oe_dashboard_column').sortable({
            connectWith: '.oe_dashboard_column',
            handle: '.oe_header',
            scroll: false
        }).bind('sortstop', function () {
            self.trigger_up('save_dashboard');
        });

        return $html;
    },

    //--------------------------------------------------------------------------
    // Handlers
    //--------------------------------------------------------------------------

    /**
     * @private
     */
    _onChangeLayout: function () {
        var currentLayout = this.$('.oe_dashboard').attr('data-layout');
        this.trigger_up('change_layout', {currentLayout: currentLayout});
    },
    /**
     * @private
     * @param {MouseEvent} event
     */
    _onCloseAction: function (event) {
        var self = this;
        var $container = $(event.currentTarget).parents('.oe_action:first');
        Dialog.confirm(this, (_t("Are you sure you want to remove this item?")), {
            confirm_callback: function () {
                $container.remove();
                self.trigger_up('save_dashboard');
            },
        });
    },
    /**
     * Intercepts (without stopping) 'do_action' events to force the
     * 'keepSearchView' option to false, as the dashboard action has no search
     * view, and thus there is no search view that could be re-used for the
     * action to execute (a new one will be created instead).
     *
     * @private
     * @param {OdooEvent} event
     */
    _onDoAction: function (event) {
        if (event.data.options) {
            event.data.options.keepSearchView = false;
        }
    },
    /**
     * Stops the propagation of 'env_updated' events triggered by the controllers
     * instantiated by the dashboard.
     *
     * @private
     */
    _onEnvUpdated: function (event) {
        event.stopPropagation();
    },
    /**
     * @private
     * @param {MouseEvent} event
     */
    _onFoldClick: function (event) {
        var $e = $(event.currentTarget);
        var $action = $e.closest('.oe_action');
        var id = $action.data('id');
        var actionAttrs = this.actionsDescr[id];

        if ($e.is('.oe_minimize')) {
            actionAttrs.fold = '1';
        } else {
            delete(actionAttrs.fold);
        }
        $e.toggleClass('oe_minimize oe_maximize');
        $action.find('.oe_content').toggle();
        this.trigger_up('save_dashboard');
    },
    /**
     * Let FormController know which form view it should display based on the
     * window action of the sub controller that is switching view
     *
     * @private
     * @param {OdooEvent} event
     */
    _onSwitchView: function (event) {
        event.data.formViewID = this._boardFormViewIDs[event.target.handle];
    },
    /**
     * Stops the propagation of 'update_filters' events triggered by the
     * controllers instantiated by the dashboard to prevent them from
     * interfering with the ActionManager.
     *
     * @private
     * @param {OdooEvent} event
     */
    _onUpdateFilters: function (event) {
        event.stopPropagation();
    },
});

var BoardView = FormView.extend({
    config: _.extend({}, FormView.prototype.config, {
        Controller: BoardController,
        Renderer: BoardRenderer,
    }),
    display_name: _lt('Board'),

    /**
     * @override
     */
    init: function (viewInfo) {
        this._super.apply(this, arguments);
        this.controllerParams.customViewID = viewInfo.custom_view_id;
    },
});

return BoardView;

});
odoo.define('sale.ProductConfiguratorFormController', function (require) {
"use strict";

var core = require('web.core');
var _t = core._t;
var FormController = require('web.FormController');
var OptionalProductsModal = require('sale.OptionalProductsModal');

var ProductConfiguratorFormController = FormController.extend({
    custom_events: _.extend({}, FormController.prototype.custom_events, {
        field_changed: '_onFieldChanged'
    }),

    /**
     * @override
     */
    start: function () {
        this.$el.addClass('o_product_configurator');
        return this._super.apply(this, arguments);
    },
    /**
     * We need to override the default click behavior for our "Add" button
     * because there is a possibility that this product has optional products.
     * If so, we need to display an extra modal to choose the options.
     *
     * @override
     */
    _onButtonClicked: function (event) {
        if (event.stopPropagation){
            event.stopPropagation();
        }
        var attrs = event.data.attrs;
        if (attrs.special === 'cancel') {
            this._super.apply(this, arguments);
        } else {
            if (!this.$el
                    .parents('.modal')
                    .find('.o_sale_product_configurator_add')
                    .hasClass('disabled')){
                this._handleAdd(this.$el);
            }
        }
    },
    /**
     * This is overridden to allow catching the "select" event on our product template select field.
     * This will not work anymore if more fields are added to the form.
     * TODO awa: Find a better way to catch that event.
     *
     * @override
     */
    _onFieldChanged: function (event) {
        this._super.apply(this, arguments);

        var self = this;
        var product_id = event.data.changes.product_template_id.id;

        // check to prevent traceback when emptying the field
        if (!product_id) {
            return;
        }

        this.$el.parents('.modal').find('.o_sale_product_configurator_add').removeClass('disabled');

        this._rpc({
            route: '/product_configurator/configure',
            params: {
                product_id: product_id,
                pricelist_id: this.renderer.pricelistId
            }
        }).then(function (configurator) {
            self.renderer.renderConfigurator(configurator);
        });
    },

    //--------------------------------------------------------------------------
    // Private
    //--------------------------------------------------------------------------

    /**
    * When the user adds a product that has optional products, we need to display
    * a window to allow the user to choose these extra options.
    *
    * This will also create the product if it's in "dynamic" mode
    * (see product_attribute.create_variant)
    *
    * @private
    * @param {$.Element} $modal
    */
    _handleAdd: function ($modal) {
        var self = this;
        var productSelector = [
            'input[type="hidden"][name="product_id"]',
            'input[type="radio"][name="product_id"]:checked'
        ];

        var productId = parseInt($modal.find(productSelector.join(', ')).first().val(), 10);
        var productReady = this.renderer.selectOrCreateProduct(
            $modal,
            productId,
            $modal.find('.product_template_id').val(),
            false
        );

        productReady.done(function (productId) {
            $modal.find(productSelector.join(', ')).val(productId);

            var variantValues = self
                .renderer
                .getSelectedVariantValues($modal.find('.js_product'));

            var productCustomVariantValues = self
                .renderer
                .getCustomVariantValues($modal.find('.js_product'));

            var noVariantAttributeValues = self
                .renderer
                .getNoVariantAttributeValues($modal.find('.js_product'));

            self.rootProduct = {
                product_id: productId,
                quantity: parseFloat($modal.find('input[name="add_qty"]').val() || 1),
                variant_values: variantValues,
                product_custom_attribute_values: productCustomVariantValues,
                no_variant_attribute_values: noVariantAttributeValues
            };

            self.optionalProductsModal = new OptionalProductsModal($('body'), {
                rootProduct: self.rootProduct,
                pricelistId: self.renderer.pricelistId,
                okButtonText: _t('Confirm'),
                cancelButtonText: _t('Back'),
                title: _t('Configure'),
                context: self.initialState.context,
            }).open();

            self.optionalProductsModal.on('options_empty', null,
                self._onModalOptionsEmpty.bind(self));

            self.optionalProductsModal.on('update_quantity', null,
                self._onOptionsUpdateQuantity.bind(self));

            self.optionalProductsModal.on('confirm', null,
                self._onModalConfirm.bind(self));
        });
    },

    /**
     * No optional products found for this product, only add the root product
     *
     * @private
     */
    _onModalOptionsEmpty: function () {
        this._addProducts([this.rootProduct]);
    },

    /**
     * Add all selected products
     *
     * @private
     */
    _onModalConfirm: function () {
        this._addProducts(this.optionalProductsModal.getSelectedProducts());
    },

    /**
     * Update product configurator form
     * when quantity is updated in the optional products window
     *
     * @private
     * @param {integer} quantity
     */
    _onOptionsUpdateQuantity: function (quantity) {
        this.$el
            .find('input[name="add_qty"]')
            .val(quantity)
            .trigger('change');
    },

    /**
    * This triggers the close action for the window and
    * adds the product as the "infos" parameter.
    * It will allow the caller (typically the SO line form) of this window
    * to handle the added products.
    *
    * @private
    * @param {Array} products the list of added products
    *   {integer} products.product_id: the id of the product
    *   {integer} products.quantity: the added quantity for this product
    *   {Array} products.product_custom_attribute_values:
    *     see product_configurator_mixin.getCustomVariantValues
    *   {Array} products.no_variant_attribute_values:
    *     see product_configurator_mixin.getNoVariantAttributeValues
    */
    _addProducts: function (products) {
        this.do_action({type: 'ir.actions.act_window_close', infos: products});
    }
});

return ProductConfiguratorFormController;

});
示例#5
0
odoo.define('barcodes.FormView', function (require) {
"use strict";

var BarcodeEvents = require('barcodes.BarcodeEvents'); // handle to trigger barcode on bus
var concurrency = require('web.concurrency');
var core = require('web.core');
var Dialog = require('web.Dialog');
var FormController = require('web.FormController');
var FormRenderer = require('web.FormRenderer');

var _t = core._t;


FormController.include({
    custom_events: _.extend({}, FormController.prototype.custom_events, {
        activeBarcode: '_barcodeActivated',
    }),

    /**
     * add default barcode commands for from view
     *
     * @override
     */
    init: function () {
        this._super.apply(this, arguments);
        this.activeBarcode = {
            form_view: {
                commands: {
                    'O-CMD.EDIT': this._barcodeEdit.bind(this),
                    'O-CMD.DISCARD': this._barcodeDiscard.bind(this),
                    'O-CMD.SAVE': this._barcodeSave.bind(this),
                    'O-CMD.PREV': this._barcodePagerPrevious.bind(this),
                    'O-CMD.NEXT': this._barcodePagerNext.bind(this),
                    'O-CMD.PAGER-FIRST': this._barcodePagerFirst.bind(this),
                    'O-CMD.PAGER-LAST': this._barcodePagerLast.bind(this),
                },
            },
        };

        this.barcodeMutex = new concurrency.Mutex();
        this._barcodeStartListening();
    },
    /**
     * @override
     */
    destroy: function () {
        this._barcodeStopListening();
        this._super();
    },

    //--------------------------------------------------------------------------
    // Private
    //--------------------------------------------------------------------------

    /**
     * @private
     * @param {string} barcode sent by the scanner (string generate from keypress series)
     * @param {Object} activeBarcode: options sent by the field who use barcode features
     * @returns {Deferred}
     */
    _barcodeAddX2MQuantity: function (barcode, activeBarcode) {
        if (this.mode === 'readonly') {
            this.do_warn(_t('Error : Document not editable'),
                _t('To modify this document, please first start edition.'));
            return new $.Deferred().reject();
        }

        var record = this.model.get(this.handle);
        var candidate = this._getBarCodeRecord(record, barcode, activeBarcode);
        if (candidate) {
            return this._barcodeSelectedCandidate(candidate, record, barcode, activeBarcode);
        } else {
            return this._barcodeWithoutCandidate(record, barcode, activeBarcode);
        }
    },
    /**
     * @private
     */
    _barcodeDiscard: function () {
        return this.discardChanges();
    },
    /**
     * @private
     */
    _barcodeEdit: function () {
        return this._setMode('edit');
    },
    /**
     * @private
     */
    _barcodePagerFirst: function () {
        var self = this;
        return this.mutex.exec(function () {}).then(function () {
            self.pager.updateState({
                current_min: 1,
            }, {notifyChange: true});
        });
    },
    /**
     * @private
     */
    _barcodePagerLast: function () {
        var self = this;
        return this.mutex.exec(function () {}).then(function () {
            var state = self.model.get(self.handle, {raw: true});
            self.pager.updateState({
                current_min: state.count,
            }, {notifyChange: true});
        });
    },
    /**
     * @private
     */
    _barcodePagerNext: function () {
        return this.mutex.exec(function () {}).then(this.pager.next.bind(this.pager));
    },
    /**
     * @private
     */
    _barcodePagerPrevious: function () {
        return this.mutex.exec(function () {}).then(this.pager.previous.bind(this.pager));
    },
    /**
     * Returns true iff the given barcode matches the given record (candidate).
     *
     * @private
     * @param {Object} candidate: record in the x2m
     * @param {string} barcode sent by the scanner (string generate from keypress series)
     * @param {Object} activeBarcode: options sent by the field who use barcode features
     * @returns {boolean}
     */
    _barcodeRecordFilter: function (candidate, barcode, activeBarcode) {
        return candidate.data.product_barcode === barcode;
    },
    /**
     * @private
     */
    _barcodeSave: function () {
        return this.saveRecord();
    },
    /**
     * @private
     * @param {Object} candidate: record in the x2m
     * @param {Object} current record
     * @param {string} barcode sent by the scanner (string generate from keypress series)
     * @param {Object} activeBarcode: options sent by the field who use barcode features
     * @returns {Deferred}
     */
    _barcodeSelectedCandidate: function (candidate, record, barcode, activeBarcode) {
        var changes = {};
        var candidateChanges = {};
        candidateChanges[activeBarcode.quantity] = candidate.data[activeBarcode.quantity] + 1;
        changes[activeBarcode.fieldName] = {
            operation: 'UPDATE',
            id: candidate.id,
            data: candidateChanges,
        };
        return this.model.notifyChanges(this.handle, changes);
    },
    /**
     * @private
     */
    _barcodeStartListening: function () {
        core.bus.on('barcode_scanned', this, this._barcodeScanned);
        core.bus.on('keypress', this, this._quantityListener);
    },
    /**
     * @private
     */
    _barcodeStopListening: function () {
        core.bus.off('barcode_scanned', this, this._barcodeScanned);
        core.bus.off('keypress', this, this._quantityListener);
    },
    /**
     * @private
     * @param {Object} current record
     * @param {string} barcode sent by the scanner (string generate from keypress series)
     * @param {Object} activeBarcode: options sent by the field who use barcode features
     * @returns {Deferred}
     */
    _barcodeWithoutCandidate: function (record, barcode, activeBarcode) {
        var changes = {};
        changes[activeBarcode.name] = barcode;
        return this.model.notifyChanges(record.id, changes);
    },
    /**
     * @private
     * @param {Object} current record
     * @param {string} barcode sent by the scanner (string generate from keypress series)
     * @param {Object} activeBarcode: options sent by the field who use barcode features
     * @returns {Object|undefined}
     */
    _getBarCodeRecord: function (record, barcode, activeBarcode) {
        var self = this;
        if (!activeBarcode.fieldName || !record.data[activeBarcode.fieldName]) {
            return;
        }
        return _.find(record.data[activeBarcode.fieldName].data, function (record) {
            return self._barcodeRecordFilter(record, barcode, activeBarcode);
        });
    },

    //--------------------------------------------------------------------------
    // Handlers
    //--------------------------------------------------------------------------

    /**
     * The barcode is activate when at least one widget trigger_up 'activeBarcode' event
     * with the widget option
     *
     * @param {OdooEvent} event
     * @param {string} event.data.name: the current field name
     * @param {string} [event.data.fieldName] optional for x2many sub field
     * @param {string} [event.data.quantity] optional field to increase quantity
     * @param {Object} [event.data.commands] optional added methods
     *     can use comand with specific barcode (with ReservedBarcodePrefixes)
     *     or change 'barcode' for all other received barcodes
     *     (e.g.: 'O-CMD.MAIN-MENU': function ..., barcode: function () {...})
     */
    _barcodeActivated: function (event) {
        event.stopPropagation();
        var name = event.data.name;
        this.activeBarcode[name] = {
            name: name,
            handle: this.handle,
            target: event.target,
            widget: event.target.attrs && event.target.attrs.widget,
            fieldName: event.data.fieldName,
            quantity: event.data.quantity,
            commands: event.data.commands || {},
            candidate: this.activeBarcode[name] && this.activeBarcode[name].handle === this.handle ?
                this.activeBarcode[name].candidate : null,
        };

        // we want to disable autofocus when activating the barcode to avoid
        // putting the scanned value in the focused field
        this.disableAutofocus = true;
    },
    /**
     * @private
     * @param {string|function} method defined by the commands options
     * @param {string} barcode sent by the scanner (string generate from keypress series)
     * @param {Object} activeBarcode: options sent by the field who use barcode features
     * @returns {Deferred}
     */
    _barcodeActiveScanned: function (method, barcode, activeBarcode) {
        var self = this;
        var methodDef;
        var def = new $.Deferred();
        if (typeof method === 'string') {
            methodDef = this[method](barcode, activeBarcode);
        } else {
            methodDef = method.call(this, barcode, activeBarcode);
        }
        methodDef
            .done(function () {
                var record = self.model.get(self.handle);
                var candidate = self._getBarCodeRecord(record, barcode, activeBarcode);
                activeBarcode.candidate = candidate;
            })
            .always(function () {
                def.resolve();
            });
        return def;
    },
    /**
     * Method called when a user scan a barcode, call each method in function of the
     * widget options then update the renderer
     *
     * @private
     * @param {string} barcode sent by the scanner (string generate from keypress series)
     * @param {DOM Object} target
     * @returns {Deferred}
     */
    _barcodeScanned: function (barcode, target) {
        var self = this;
        return this.barcodeMutex.exec(function () {
            var prefixed = _.any(BarcodeEvents.ReservedBarcodePrefixes,
                    function (reserved) {return barcode.indexOf(reserved) === 0;});
            var hasCommand = false;
            var defs = [];
            for (var k in self.activeBarcode) {
                var activeBarcode = self.activeBarcode[k];
                // Handle the case where there are several barcode widgets on the same page. Since the
                // event is global on the page, all barcode widgets will be triggered. However, we only
                // want to keep the event on the target widget.
                if (! $.contains(target, self.el)) {
                    continue;
                }

                var methods = self.activeBarcode[k].commands;
                var method = prefixed ? methods[barcode] : methods.barcode;
                if (method) {
                    if (prefixed) {
                        hasCommand = true;
                    }
                    defs.push(self._barcodeActiveScanned(method, barcode, activeBarcode));
                }
            }
            if (prefixed && !hasCommand) {
                self.do_warn(_t('Error : Barcode command is undefined'), barcode);
            }
            return self.alive($.when.apply($, defs)).then(function () {
                if (!prefixed) {
                    // redraw the view if we scanned a real barcode (required if
                    // we manually apply the change in JS, e.g. incrementing the
                    // quantity)
                    self.update({}, {reload: false});
                }
            });
        });
    },
    /**
     * @private
     * @param {KeyEvent} event
     */
    _quantityListener: function (event) {
        var character = String.fromCharCode(event.which);

        // only catch the event if we're not focused in
        // another field and it's a number
        if (!$(event.target).is('body') || !/[0-9]/.test(character)) {
            return;
        }

        var barcodeInfos = _.filter(this.activeBarcode, 'setQuantityWithKeypress');
        if (!barcodeInfos.length) {
            return;
        }

        if (!_.compact(_.pluck(barcodeInfos, 'candidate')).length) {
            return this.do_warn(_t('Error : No last scanned barcode'),
                _t('To set the quantity please scan a barcode first.'));
        }

        for (var k in this.activeBarcode) {
            if (this.activeBarcode[k].candidate) {
                this._quantityOpenDialog(character, this.activeBarcode[k]);
            }
        }
    },
    /**
     * @private
     * @param {string} character
     * @param {Object} activeBarcode: options sent by the field who use barcode features
     */
    _quantityOpenDialog: function (character, activeBarcode) {
        var self = this;
        var $content = $('<div>').append($('<input>', {type: 'text', class: 'o_set_qty_input'}));
        this.dialog = new Dialog(this, {
            title: _t('Set quantity'),
            buttons: [{text: _t('Select'), classes: 'btn-primary', close: true, click: function () {
                var new_qty = this.$content.find('.o_set_qty_input').val();
                var values = {};
                values[activeBarcode.quantity] = parseFloat(new_qty);
                return self.model.notifyChanges(activeBarcode.candidate.id, values).then(function () {
                    self.update({}, {reload: false});
                });
            }}, {text: _t('Discard'), close: true}],
            $content: $content,
        });
        this.dialog.opened().then(function () {
            // This line set the value of the key which triggered the _set_quantity in the input
            var $input = self.dialog.$('.o_set_qty_input').focus().val(character);
            var $selectBtn = self.dialog.$footer.find('.btn-primary');
            $input.on('keypress', function (event){
                if (event.which === 13) {
                    event.preventDefault();
                    $input.off();
                    $selectBtn.click();
                }
            });
        });
        this.dialog.open();
    },
});


FormRenderer.include({

    //--------------------------------------------------------------------------
    // Private
    //--------------------------------------------------------------------------
    /**
     * trigger_up 'activeBarcode' to Add barcode event handler
     *
     * @private
     * @param {jQueryElement} $button
     * @param {Object} node
     */
    _barcodeButtonHandler: function ($button, node) {
        var commands = {};
        commands.barcode = function () {return $.when();};
        commands['O-BTN.' + node.attrs.barcode_trigger] = function () {
            if (!$button.hasClass('o_invisible_modifier')) {
                $button.click();
            }
            return $.when();
        };
        this.trigger_up('activeBarcode', {
            name: node.attrs.name,
            commands: commands
        });
    },
    /**
     * Add barcode event handler
     *
     * @override
     * @private
     * @param {Object} node
     * @returns {jQueryElement}
     */
    _renderHeaderButton: function (node) {
        var $button = this._super.apply(this, arguments);
        if (node.attrs.barcode_trigger) {
            this._barcodeButtonHandler($button, node);
        }
        return $button;
    },
    /**
     * Add barcode event handler
     *
     * @override
     * @private
     * @param {Object} node
     * @returns {jQueryElement}
     */
    _renderStatButton: function (node) {
        var $button = this._super.apply(this, arguments);
        if (node.attrs.barcode_trigger) {
            this._barcodeButtonHandler($button, node);
        }
        return $button;
    },
    /**
     * Add barcode event handler
     *
     * @override
     * @private
     * @param {Object} node
     * @returns {jQueryElement}
     */
    _renderTagButton: function (node) {
        var $button = this._super.apply(this, arguments);
        if (node.attrs.barcode_trigger) {
            this._barcodeButtonHandler($button, node);
        }
        return $button;
    }
});

BarcodeEvents.ReservedBarcodePrefixes.push('O-BTN');

});