Update list_search.js to search all records using server-side filtering
This commit is contained in:
parent
ab99a15852
commit
d9a8bbcdd2
|
|
@ -1,12 +1,16 @@
|
||||||
odoo.define('fims_row_no_header_fix_tree_view.list_search', function (require) {
|
odoo.define('fims_general_search_tree_view.list_search', function (require) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var ListRenderer = require('web.ListRenderer');
|
var ListRenderer = require('web.ListRenderer');
|
||||||
|
var core = require('web.core');
|
||||||
|
var _t = core._t;
|
||||||
|
|
||||||
ListRenderer.include({
|
ListRenderer.include({
|
||||||
events: _.extend({
|
events: _.extend({
|
||||||
'keyup .oe_search_input': '_onKeyUp'
|
'keyup .oe_search_input': '_onKeyUp',
|
||||||
|
'change .oe_search_input': '_onSearchChange'
|
||||||
}, ListRenderer.prototype.events),
|
}, ListRenderer.prototype.events),
|
||||||
|
|
||||||
_renderView: function () {
|
_renderView: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
return this._super.apply(this, arguments).then(function () {
|
return this._super.apply(this, arguments).then(function () {
|
||||||
|
|
@ -14,43 +18,217 @@ odoo.define('fims_row_no_header_fix_tree_view.list_search', function (require) {
|
||||||
if (self.arch.tag == 'tree' && self.$el.hasClass('o_list_view')) {
|
if (self.arch.tag == 'tree' && self.$el.hasClass('o_list_view')) {
|
||||||
// Check if the search input already exists
|
// Check if the search input already exists
|
||||||
if (!self.$el.find('.oe_search_input').length) {
|
if (!self.$el.find('.oe_search_input').length) {
|
||||||
var search = '<input type="text" class="oe_search_input mt-2 ml-2 pl-3" placeholder="Search...">';
|
var search = '<div class="oe_search_container" style="display: flex; align-items: center; margin: 8px;">' +
|
||||||
|
'<input type="text" class="oe_search_input" placeholder="' + _t('Search in all records...') + '" style="flex: 1;">' +
|
||||||
|
'<button class="btn btn-sm btn-secondary oe_clear_search ml-2" style="display: none;">' + _t('Clear') + '</button>' +
|
||||||
|
'<span class="oe_search_count ml-2" style="display: none;"></span>' +
|
||||||
|
'</div>';
|
||||||
self.$el.find('table').addClass('oe_table_search');
|
self.$el.find('table').addClass('oe_table_search');
|
||||||
var $search = $(search).css('border', '1px solid #ccc')
|
var $search = $(search);
|
||||||
.css('width', '99%')
|
$search.find('.oe_search_input').css({
|
||||||
.css('height', '28px')
|
'border': '1px solid #ccc',
|
||||||
|
'height': '32px',
|
||||||
|
'padding': '0 12px',
|
||||||
|
'border-radius': '4px'
|
||||||
|
});
|
||||||
|
$search.find('.oe_clear_search').on('click', function() {
|
||||||
|
self._clearSearch();
|
||||||
|
});
|
||||||
self.$el.prepend($search);
|
self.$el.prepend($search);
|
||||||
|
|
||||||
|
// Store original domain for restoration
|
||||||
|
self._originalDomain = self.state.domain || [];
|
||||||
|
self._searchTimeout = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
_onKeyUp: function (event) {
|
_onKeyUp: function (event) {
|
||||||
var value = $(event.currentTarget).val().toLowerCase();
|
var self = this;
|
||||||
var count_row = 0;
|
var value = $(event.currentTarget).val();
|
||||||
var $el = $(this.$el)
|
|
||||||
$(".oe_table_search tr:not(:first)").filter(function() {
|
// Clear previous timeout
|
||||||
$(this).toggle(arabicCaseInsensitiveSearch($(this).text(),value))
|
if (this._searchTimeout) {
|
||||||
count_row = arabicCaseInsensitiveSearch($(this).text(),value) ? count_row+1 : count_row
|
clearTimeout(this._searchTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add delay to avoid too many requests
|
||||||
|
this._searchTimeout = setTimeout(function() {
|
||||||
|
self._performSearch(value);
|
||||||
|
}, 500); // 500ms delay
|
||||||
|
},
|
||||||
|
|
||||||
|
_onSearchChange: function (event) {
|
||||||
|
var value = $(event.currentTarget).val();
|
||||||
|
this._performSearch(value);
|
||||||
|
},
|
||||||
|
|
||||||
|
_performSearch: function (searchValue) {
|
||||||
|
var self = this;
|
||||||
|
var value = searchValue.toLowerCase().trim();
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
this._clearSearch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show/hide clear button
|
||||||
|
this.$el.find('.oe_clear_search').toggle(!!value);
|
||||||
|
|
||||||
|
// Build search domain
|
||||||
|
var searchDomain = this._buildSearchDomain(value);
|
||||||
|
|
||||||
|
// Combine with original domain
|
||||||
|
var newDomain = this._originalDomain.concat(searchDomain);
|
||||||
|
|
||||||
|
// Update the domain and reload
|
||||||
|
this.trigger_up('reload', {
|
||||||
|
domain: newDomain,
|
||||||
|
offset: 0,
|
||||||
|
limit: this.state.limit,
|
||||||
|
keepChanges: false,
|
||||||
|
reload: true,
|
||||||
|
callback: function() {
|
||||||
|
self._updateSearchCount(value);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_buildSearchDomain: function(value) {
|
||||||
|
var self = this;
|
||||||
|
var domain = [];
|
||||||
|
var searchableFields = this._getSearchableFields();
|
||||||
|
|
||||||
|
if (searchableFields.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize search value for Arabic
|
||||||
|
var normalizedValue = this._normalizeArabic(value);
|
||||||
|
|
||||||
|
// Build OR domain for all searchable fields
|
||||||
|
var orConditions = [];
|
||||||
|
_.each(searchableFields, function(field) {
|
||||||
|
// For char, text, and name fields
|
||||||
|
if (['char', 'text', 'html'].includes(field.type)) {
|
||||||
|
orConditions.push([field.name, 'ilike', value]);
|
||||||
|
// Also search with normalized value if different
|
||||||
|
if (normalizedValue !== value) {
|
||||||
|
orConditions.push([field.name, 'ilike', normalizedValue]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// For many2one fields (search in display_name)
|
||||||
|
else if (field.type === 'many2one') {
|
||||||
|
orConditions.push([field.name + '.display_name', 'ilike', value]);
|
||||||
|
}
|
||||||
|
// For number fields (if value is numeric)
|
||||||
|
else if (['integer', 'float', 'monetary'].includes(field.type) && !isNaN(value)) {
|
||||||
|
orConditions.push([field.name, '=', parseFloat(value)]);
|
||||||
|
}
|
||||||
|
// For selection fields
|
||||||
|
else if (field.type === 'selection') {
|
||||||
|
orConditions.push([field.name, 'ilike', value]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create OR domain
|
||||||
|
if (orConditions.length > 0) {
|
||||||
|
// Add '|' operators for OR condition
|
||||||
|
for (var i = 1; i < orConditions.length; i++) {
|
||||||
|
domain.push('|');
|
||||||
|
}
|
||||||
|
domain = domain.concat(orConditions);
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain;
|
||||||
|
},
|
||||||
|
|
||||||
|
_getSearchableFields: function() {
|
||||||
|
var self = this;
|
||||||
|
var fields = [];
|
||||||
|
|
||||||
|
// Get visible fields from the list view
|
||||||
|
_.each(this.columns, function(column) {
|
||||||
|
if (!column.invisible && column.attrs.name) {
|
||||||
|
var fieldName = column.attrs.name;
|
||||||
|
var field = self.state.fields[fieldName];
|
||||||
|
if (field && field.searchable !== false) {
|
||||||
|
fields.push({
|
||||||
|
name: fieldName,
|
||||||
|
type: field.type,
|
||||||
|
string: field.string
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// If no visible fields, get all char/text fields
|
||||||
|
if (fields.length === 0) {
|
||||||
|
_.each(this.state.fields, function(field, fieldName) {
|
||||||
|
if (['char', 'text', 'many2one'].includes(field.type) && field.searchable !== false) {
|
||||||
|
fields.push({
|
||||||
|
name: fieldName,
|
||||||
|
type: field.type,
|
||||||
|
string: field.string
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
},
|
||||||
|
|
||||||
|
_clearSearch: function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// Clear input
|
||||||
|
this.$el.find('.oe_search_input').val('');
|
||||||
|
this.$el.find('.oe_clear_search').hide();
|
||||||
|
this.$el.find('.oe_search_count').hide();
|
||||||
|
|
||||||
|
// Restore original domain
|
||||||
|
this.trigger_up('reload', {
|
||||||
|
domain: this._originalDomain,
|
||||||
|
offset: 0,
|
||||||
|
limit: this.state.limit,
|
||||||
|
keepChanges: false,
|
||||||
|
reload: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateSearchCount: function(searchValue) {
|
||||||
|
var count = this.state.count || 0;
|
||||||
|
var $countEl = this.$el.find('.oe_search_count');
|
||||||
|
|
||||||
|
if (searchValue && count >= 0) {
|
||||||
|
$countEl.text(_t('Found: ') + count + _t(' records')).show();
|
||||||
|
} else {
|
||||||
|
$countEl.hide();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_normalizeArabic: function(text) {
|
||||||
|
if (!text) return text;
|
||||||
|
|
||||||
|
// Normalizing Arabic text by removing common variations
|
||||||
|
return text
|
||||||
|
.replace(/[\u064B-\u065F]/g, '') // Remove diacritics
|
||||||
|
.replace(/[\u0660-\u0669]/g, (d) => String.fromCharCode(d.charCodeAt(0) - 0x0660 + 0x0030)) // Convert Arabic-Indic digits
|
||||||
|
.replace(/[\u06F0-\u06F9]/g, (d) => String.fromCharCode(d.charCodeAt(0) - 0x06F0 + 0x0030)) // Extended Arabic-Indic digits
|
||||||
|
.replace(/[\u0622\u0623\u0625\u0627]/g, 'ا') // Normalize Alef forms
|
||||||
|
.replace(/[\u0629]/g, 'ه') // Normalize Teh Marbuta to Heh
|
||||||
|
.replace(/[\u064A\u0626\u0649]/g, 'ي') // Normalize Yeh forms
|
||||||
|
.replace(/[\u0624\u0648]/g, 'و'); // Normalize Waw variants
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function () {
|
||||||
|
// Clear timeout if exists
|
||||||
|
if (this._searchTimeout) {
|
||||||
|
clearTimeout(this._searchTimeout);
|
||||||
|
}
|
||||||
|
return this._super.apply(this, arguments);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function normalizeArabic(text) {
|
|
||||||
// Normalizing Arabic text by removing common variations
|
|
||||||
return text
|
|
||||||
.replace(/[\u064B-\u065F]/g, '') // Remove diacritics
|
|
||||||
.replace(/[\u0660-\u0669]/g, (d) => String.fromCharCode(d.charCodeAt(0) - 0x0660 + 0x0030)) // Convert Arabic-Indic digits to Latin digits
|
|
||||||
.replace(/[\u06F0-\u06F9]/g, (d) => String.fromCharCode(d.charCodeAt(0) - 0x06F0 + 0x0030)) // Convert Extended Arabic-Indic digits to Latin digits
|
|
||||||
.replace(/[\u0622\u0623\u0625\u0627]/g, 'ا') // Normalize different forms of Alef
|
|
||||||
.replace(/[\u0629]/g, 'ه') // Normalize Teh Marbuta to Heh
|
|
||||||
.replace(/[\u064A\u0626\u0649]/g, 'ي') // Normalize different forms of Yeh
|
|
||||||
.replace(/[\u0624\u0648]/g, 'و'); // Normalize Waw and its variants
|
|
||||||
}
|
|
||||||
|
|
||||||
function arabicCaseInsensitiveSearch(text, searchTerm) {
|
|
||||||
const normalizedText = normalizeArabic(text).toLowerCase();
|
|
||||||
const normalizedSearchTerm = normalizeArabic(searchTerm).toLowerCase();
|
|
||||||
return normalizedText.indexOf(normalizedSearchTerm) > -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue