Enable Dark Mode!
how-to-create-a-progress-bar-in-odoo-14.jpg
By: Hajaj Roshan

How To Create a Progress Bar in Odoo 14?

Technical Odoo 14

In Odoo, we can utilize the "progressbar" widget to add a progress bar in our view. This default progress bar is quite basic and we can't tweak it as we want. So, if we want to add a progress bar with specific colours and parameters we can create a new widget and apply it to our field.

In this blog, the aspects of using the "progressbar" widget to add a progress bar in the view of different menus in odoo is being explained in detail.

Let’s get straight to the coding part. The following image depicts the structure of the module that we are going to create.

how-to-create-a-progress-bar-in-odoo-14

Progress bar structure is defined in the progress_bar.xml file and the rest of the coding lies in the progress_bar.js for actions such as computing the percentage progress.

Let’s first create the  progress_bar.xml file, here a new template is defined with the name “NewProgressBar”. As you can see there are two div classes, o_progress, and o_progressbar_complete. Here, the ‘o_progress’ div will act as a background for the ‘o_progressbar_complete’ div, showing the actual progress. In the style attribute, you can see that here, I have given the background color and height of the progress bar. Moreover, you can modify these style parameters as you like.

XML FIle: progress_bar.xml

<templates
id="template" xml:space="preserve">
    <t t-name="NewProgressBar">
        <div>
            <div class="o_progress" style="background-color: #c1c1dd; height: 7px;">
                <div class="o_progressbar_complete" style="background-color: #6f6f62;  height: 7px;"/>
            </div>
        </div>
    </t>
</templates>

Now let’s come to JavaScript where we are extending ‘AbstractField’ and adding the class ‘ProgressBarNew’ for our progress bar. Additionally, we have also specified the XML template that we created in the previous step using the template name ‘NewProgressBar’. In the supported field types, we mentioned Integer and Float. However, typically we used to use float values for displaying the progress.

JavaScript File: progress_bar.js

odoo.define('progress_bar.ProgressBarNew',
function(require){
    "use strict";
 
    var AbstractField = require('web.AbstractField');
    var field_registry = require('web.field_registry');
    var core = require('web.core');
    var _t = core._t;
 
    var ProgressBarNew = AbstractField.extend({
        template: "NewProgressBar",
        supportedFieldTypes: ['integer', 'float'],
 
        start: function(){
            this._super.apply(this, arguments);
 
            if (this.recordData[this.nodeOptions.currentValue]){
                this.value = this.recordData[this.nodeOptions.currentValue];
            }
 
            // The few next lines determine if the widget can write on the record or not
            this.editable_readonly = !!this.nodeOptions.editable_readonly;
            // "hard" readonly
            this.readonly = this.nodeOptions.readonly || !this.nodeOptions.editable;
 
            this.canWrite = !this.readonly && (
                this.mode === 'edit' ||
                (this.editable_readonly && this.mode === 'readonly') ||
                (this.viewType === 'kanban') // Keep behavior before commit
            );
 
            // Boolean to toggle if we edit the numerator (value) or the denominator (max_value)
            this.edit_max_value = !!this.nodeOptions.edit_max_value;
            this.max_value = this.recordData[this.nodeOptions.max_value] || 100;
 
            this.title = _t(this.attrs.title || this.nodeOptions.title) || '';
 
            this.write_mode = false;
        },
 
        _render: function (){
            var self = this;
            this._render_value();
 
            if (this.canWrite) {
                if (this.edit_on_click) {
                    this.$el.on('click', '.o_progress', function (e) {
                        var $target = $(e.currentTarget);
                        var numValue = Math.floor((e.pageX - $target.offset().left) / $target.outerWidth() * self.max_value);
                        self.on_update(numValue);
                        self._render_value();
                    });
                } else {
                    this.$el.on('click', function () {
                        if (!self.write_mode) {
                            var $input = $('<input>', {type: 'text', class: 'o_progressbar_value o_input'});
                            $input.on('blur', self.on_change_input.bind(self));
                            self.$('.o_progressbar_value').replaceWith($input);
                            self.write_mode = true;
                            self._render_value();
                        }
                    });
                }
            }
            return this._super();
        },
 
        on_update: function (value) {
            if (this.edit_max_value) {
                this.max_value = value;
                this._isValid = true;
                var changes = {};
                changes[this.nodeOptions.max_value] = this.max_value;
                this.trigger_up('field_changed', {
                    dataPointID: this.dataPointID,
                    changes: changes,
                });
            } else {
                // _setValues accepts string and will parse it
                var formattedValue = this._formatValue(value);
                this._setValue(formattedValue);
            }
        },
 
        _render_value: function (v) {
            var value = this.value;
            var max_value = this.max_value;
 
            if (!isNaN(v)) {
                if (this.edit_max_value) {
                    max_value = v;
                } else {
                    value = v;
                }
            }
            value = value || 0;
            max_value = max_value || 0;
 
            var widthComplete;
            if (value <= max_value) {
                widthComplete = value/max_value * 100;
            } else {
                widthComplete = 100;
            }
 
            this.$('.o_progress').toggleClass('o_progress_overflow', value > max_value)
                .attr('aria-valuemin', '0')
                .attr('aria-valuemax', max_value)
                .attr('aria-valuenow', value);
 
            this.$('.o_progressbar_complete').css('width', widthComplete + '%');
 
        },
 
        _reset: function () {
            this._super.apply(this, arguments);
            var new_max_value = this.recordData[this.nodeOptions.max_value];
            this.max_value =  new_max_value !== undefined ? new_max_value : this.max_value;
        },
        isSet: function () {
            return true;
        },
    });
 
    field_registry.add('progress_bar_new', ProgressBarNew)
});

The _render_value() function is where the width of the progress bar is determined. The width of the o_progress div which acts as the background will be the max_value, and the width of the o_progressbar_complete div will be the computed progress value. Finally, we have to add this class to the registry field_registry.add('progress_bar_new', ProgressBarNew) and we can apply this widget using the name 'progress_bar_new'.

We need to add this JavaScript file along with the backend assets. For that let’s create an ‘assets.xml’ file and mention that in our manifest.

<odoo>
    <template id ="assets_backend" inherit_id ="web.assets_backend" name ="Website Backend Assets">
        <xpath expr="//script[last ()]" position="after">
            <script type="text/javascript" src = "/progress_bar/static/src/js/progress_bar.js"/>
        </xpath>
    </template>
</odoo>

Let’s specify both XML files in the __manifest__.py.

'data': [
        'views/assets.xml'
    ],
    'qweb': [
        'static/src/xml/progress_bar.xml'
    ],

Now let’s say we have a model with two fields start_date and end_date. And we need to display a progress bar between these days based on the current date. For that, we are creating a new progress field of type Float, the value will be computed using a function.

start_date = fields.Datetime()
end_date = fields.Datetime()
progress = fields.Float(compute=_compute_progress)
 
And the function would be,
 
    def _compute_progress(self):
        for rec in self:
            rec.progress = 0
            if rec.start_date and rec.end_date:
                total_days = rec.end_date - rec.start_date
                progress = rec.end_date - fields.Datetime.today()
                try:
                    percentage = (total_days.days - progress.days) * 100 / total_days.days
                    rec.progress = percentage
                except ZeroDivisionError:
                    rec.progress = 0

Add this ‘progress’ field on the corresponding view and apply our progress_bar_new' widget.

<field name="progress" widget="progress_bar_new"/>

The output will be,

how-to-create-a-progress-bar-in-odoo-14

In this manner we will be able to define the progress bars at the various aspects of the Odoo platform.


If you need any assistance in odoo, we are online, please chat with us.



0
Comments



Leave a comment

 


whatsapp
location

Calicut

Cybrosys Technologies Pvt. Ltd.
Neospace, Kinfra Techno Park
Kakkancherry, Calicut
Kerala, India - 673635

location

Kochi

Cybrosys Technologies Pvt. Ltd.
1st Floor, Thapasya Building,
Infopark, Kakkanad,
Kochi, India - 682030.

location

Bangalore

Cybrosys Techno Solutions
The Estate, 8th Floor,
Dickenson Road,
Bangalore, India - 560042

Send Us A Message