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](https://www.images.cybrosys.com/blog/Uploads/BlogImage/how-to-create-a-progress-bar-in-odoo-14-1.png)
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](https://www.images.cybrosys.com/blog/Uploads/BlogImage/how-to-create-a-progress-bar-in-odoo-14-2.png)
In this manner we will be able to define the
progress bars at the various aspects of the Odoo platform.