Odoo 16 Development Book

Custom widgets

In Odoo, there are various field widgets that help to provide certain functionality to our fields. For example, we can use widget=” date” for date fields, widget=” image” for binary fields to display an image, and so on.

Let’s see how we can create a custom widget with our desired functionalities in Odoo 16.

Let’s take an example of creating a widget called “one2many_delete” which will help to delete multiple one2many lines from the view.

To create our custom widget, we can perform the steps listed below.

1. First, we need to add a static/src/js/widget.js file inside our module.

odoo.define('one2many_mass_select_delete.form_widgets', function (require) {
    "use strict";
    var core = require('web.core');
    var utils = require('web.utils');
    var fieldRegistry = require('web.field_registry');
    var ListRenderer = require('web.ListRenderer');
    var rpc = require('web.rpc');
    var FieldOne2Many = require('web.relational_fields').FieldOne2Many;
    var _t = core._t;

2. For selecting multiple one2many lines, we need to add a selector/ checkbox in each line, and also for creating the widget for one2many fields, we have to extend the relational field class.

odoo.define('one2many_mass_select_delete.form_widgets', function (require) {
    "use strict";
    var core = require('web.core');
    var utils = require('web.utils');
    var fieldRegistry = require('web.field_registry');
    var ListRenderer = require('web.ListRenderer');
    var rpc = require('web.rpc');
    var FieldOne2Many = require('web.relational_fields').FieldOne2Many;
    var _t = core._t;
    ListRenderer.include({
   	 _updateSelection: function () {
        	this.selection = [];
        	var self = this;
        	var $inputs = this.$('tbody .o_list_record_selector input:visible:not(:disabled)');
        	var allChecked = $inputs.length > 0;
        	$inputs.each(function (index, input) {
            	if (input.checked) {
                	self.selection.push($(input).closest('tr').data('id'));
            	} else {
                	allChecked = false;
            	}
        	});
        	if(this.selection.length > 0){
       		 $('.button_delete_order_lines').show()
            	$('.button_select_order_lines').show()
        	}else{
       		 $('.button_delete_order_lines').hide()
       		 $('.button_select_order_lines').hide()
         	}
        	this.$('thead .o_list_record_selector input').prop('checked', allChecked);
        	this.trigger_up('selection_changed', { selection: this.selection });
        	this._updateFooter();
    	},
    })
	var One2manyDelete = FieldOne2Many.extend({
   	 template: 'One2manyDelete',
   	 events: {
   		 "click .button_delete_order_lines": "delete_selected_lines",
   		 "click .button_select_order_lines": "selected_lines",
   	 },
   	 init: function() {
        	this._super.apply(this, arguments);
    	},
   	 delete_selected_lines: function()
   	 {
   		 var self=this;
   		 var current_model = this.recordData[this.name].model;
   		 var selected_lines = self.find_deleted_lines();
   		 if (selected_lines.length === 0)
   		 {
   			 return this.displayNotification({ message: _t('Please Select at least One Record.'), type: 'danger' });
   		 }
   		 var w_response = confirm("Dou You Want to Delete ?");
   		 if (w_response) {
            	rpc.query({
                	'model': current_model,
                	'method': 'unlink',
                	'args': [selected_lines],
            	}).then(function(result){
                	self.trigger_up('reload');
            	});
         	}
   	 },
   	 selected_lines: function()
   	 {
   		 var self=this;
   		 var current_model = this.recordData[this.name].model;
   		 var selected_lines = self.find_selected_lines();
   		 if (selected_lines.length === 0)
   		 {
   			 return this.displayNotification({ message: _t('Please Select at least One Record.'), type: 'danger' });
   		 }
   		 var w_response = confirm("Dou You Want to Select ?");
   		 if (w_response) {
   		 rpc.query({
            	'model': current_model,
            	'method': 'unlink',
            	'args': [selected_lines],
        	}).then(function(result){
            	self.trigger_up('reload');
        	});
        	}
   	 },
   	 _getRenderer: function () {
        	if (this.view.arch.tag === 'kanban') {
            	return One2ManyKanbanRenderer;
        	}
        	if (this.view.arch.tag === 'tree') {
            	return ListRenderer.extend({
                	init: function (parent, state, params) {
                    	this._super.apply(this, arguments);
                    	this.hasSelectors = true;
                	},
            	});
        	}
        	return this._super.apply(this, arguments);
    	},
   	 find_deleted_lines: function () {
        	var self=this;
        	var selected_list =[];
        	this.$el.find('td.o_list_record_selector input:checked')
                	.closest('tr').each(function () {
                    	selected_list.push(parseInt(self._getResId($(this).data('id'))));
        	});
        	return selected_list;
    	},
   	 find_selected_lines: function ()
   	{   var self = this;
       	var selected_list =[];
   		var selected_list1 =[];
   		var selected_list2 =[];
       	this.$el.find('td.o_list_record_selector input:checked')
               	.closest('tr').each(function () {
              		 selected_list.push(parseInt(self._getResId($(this).data('id'))));
      				 });
   		if (selected_list.length != 0) {
   			this.$el.find('td.o_list_record_selector')
   				.closest('tr').each(function () {
   				selected_list1.push(parseInt(self._getResId($(this).data('id'))));
   			});
   			selected_list2 = selected_list1.filter(function (x) {
   				return selected_list.indexOf(x) < 0
   			});
   		}
   		return selected_list2;
   	},
	_getResId: function (recordId) {
   	var record;
   	utils.traverse_records(this.recordData[this.name], function (r) {
   	if (r.id === recordId) {
        	record = r;
   	}
    	});
        	return record.res_id;
    	},
    });
    fieldRegistry.add('one2many_delete', One2manyDelete);
});

3. Then load the js file inside the assets in the manifest as shown below.

'assets': {
    	'web.assets_backend': [
        	'one2many_mass_select_delete/static/src/js/widget.js',
    	],
    	'web.assets_qweb': [
        	'one2many_mass_select_delete/static/src/xml/widget_view.xml',
    	],
	},

4. Now we need to add the template inside static/src/Xml.

<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
    <t t-name="One2manyDelete">
   	 <div>
   		 <button t-if="!widget.get('effective_readonly')" style="margin-bottom: 1%;display:none;"
                	class="button_delete_order_lines"
                	href="javascript:void(0)">
   				 <span class="fa fa-trash"/>
   			 </button>
        	<button t-if="!widget.get('effective_readonly')" style="margin-bottom: 1%;display:none;"
                	class="button_select_order_lines"
                	href="javascript:void(0)">
   				 <span class="fa fa-check"/>
   			 </button>
        	<t t-if="widget.get('effective_readonly')">
   			 <span class="oe_form_char_content"></span>
   		 </t>
   	 </div>
	</t>
</templates>

5. Finally, we need to add this “one2many_delete” widget to the field.

<xpath expr="//field[@name='order_line']" position="attributes">
    <attribute name="widget">one2many_delete</attribute>
</xpath>
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