Fix: Enhanced General Search filtering in Tree/List Views
Merge pull request #4426 from expsa/eltayar Enhanced General Search functionality in Tree/List Views: - Fixed DOM filtering logic to ensure list view updates with filtered records - Enhanced row ID detection with 7 different methods for better compatibility - Added multiple rendering fallback methods for different Odoo versions - Improved search result processing and synchronization between counter and list - Enhanced Arabic text support and client-side search fallback
This commit is contained in:
commit
4333b0460e
|
|
@ -30,8 +30,9 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
|
|||
_renderView: function () {
|
||||
var self = this;
|
||||
|
||||
// IMPORTANT: Don't reset search state, just preserve it
|
||||
var hasActiveSearch = this._search && this._search.value;
|
||||
// Store search state before render
|
||||
var searchValue = this._search ? this._search.value : '';
|
||||
var hasActiveSearch = searchValue && searchValue.length > 0;
|
||||
|
||||
return this._super.apply(this, arguments).then(function () {
|
||||
// Add search box if needed
|
||||
|
|
@ -43,15 +44,16 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
|
|||
self._addSearchBox();
|
||||
}
|
||||
|
||||
// Always restore search state after render
|
||||
// Always restore search UI
|
||||
self._restoreSearchUI();
|
||||
|
||||
// If we have an active search, apply the filter
|
||||
if (hasActiveSearch && self._search.filteredIds) {
|
||||
// Apply filter after DOM is ready
|
||||
// If we had an active search, re-apply it after a short delay
|
||||
if (hasActiveSearch) {
|
||||
setTimeout(function() {
|
||||
self._reapplySearchFilter();
|
||||
}, 0);
|
||||
if (self._search && self._search.value) {
|
||||
self._performSearch(self._search.value);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -115,21 +117,6 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Reapply search filter after re-render
|
||||
*/
|
||||
_reapplySearchFilter: function() {
|
||||
if (!this._search || !this._search.filteredIds) return;
|
||||
|
||||
// If we have all filtered records, update the view
|
||||
if (this._search.allFilteredRecords) {
|
||||
this._updateViewWithFilteredData(this._search.allFilteredRecords, false);
|
||||
} else {
|
||||
// Just apply DOM filter
|
||||
this._applyDOMFilter(this._search.filteredIds);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle search input event (immediate tracking)
|
||||
*/
|
||||
|
|
@ -263,213 +250,292 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Update the view with filtered results
|
||||
this._updateViewWithFilteredData(records, true);
|
||||
// Update the view with filtered results - CRITICAL FIX
|
||||
this._updateViewWithFilteredData(records);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update view with filtered data - THE KEY METHOD
|
||||
* Update view with filtered data - COMPLETELY REWRITTEN
|
||||
*/
|
||||
_updateViewWithFilteredData: function(records, updateState) {
|
||||
_updateViewWithFilteredData: function(records) {
|
||||
var self = this;
|
||||
var limit = this.state.limit || 80;
|
||||
var pageRecords = records.slice(0, limit);
|
||||
|
||||
// Method 1: Try DOM filtering first (fastest, preserves input)
|
||||
var domFilterSuccess = this._tryDOMFiltering(this._search.filteredIds, limit);
|
||||
// Method 1: Try to filter existing DOM rows first
|
||||
var domFilterWorked = this._filterExistingRows();
|
||||
|
||||
if (domFilterSuccess) {
|
||||
// DOM filtering worked, just update pager
|
||||
if (domFilterWorked) {
|
||||
// Update pager with correct count
|
||||
this._updatePager(records.length);
|
||||
return;
|
||||
}
|
||||
|
||||
// Method 2: Need to update data and re-render
|
||||
// This happens when DOM doesn't have the records we need
|
||||
if (updateState && this.state && this.state.data) {
|
||||
// Update state with filtered data
|
||||
this.state.data.records = pageRecords;
|
||||
this.state.count = records.length;
|
||||
this.state.res_ids = this._search.filteredIds;
|
||||
|
||||
// Clear and re-render
|
||||
this._clearTableBody();
|
||||
this._renderFilteredRows();
|
||||
}
|
||||
// Method 2: Update the actual data and re-render
|
||||
this._updateStateAndRerender(records);
|
||||
},
|
||||
|
||||
/**
|
||||
* Try DOM filtering first (fastest method)
|
||||
* Filter existing rows in DOM - ENHANCED METHOD
|
||||
*/
|
||||
_tryDOMFiltering: function(filteredIds, limit) {
|
||||
var $rows = this.$('.o_data_row');
|
||||
if ($rows.length === 0) {
|
||||
return false; // No rows to filter
|
||||
_filterExistingRows: function() {
|
||||
if (!this._search.filteredIds || this._search.filteredIds.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var $allRows = this.$('.o_data_row');
|
||||
var limit = this.state.limit || 80;
|
||||
var visibleCount = 0;
|
||||
var foundMatch = false;
|
||||
var foundAnyMatch = false;
|
||||
|
||||
$rows.each(function() {
|
||||
console.log('Filtering rows. Total rows:', $allRows.length, 'Filtered IDs:', this._search.filteredIds);
|
||||
|
||||
// First pass: hide all rows
|
||||
$allRows.hide();
|
||||
|
||||
// Second pass: show matching rows up to limit
|
||||
$allRows.each(function(index) {
|
||||
var $row = $(this);
|
||||
var rowId = self._getRowId($row);
|
||||
|
||||
if (rowId && filteredIds.includes(rowId)) {
|
||||
console.log('Row', index, 'ID:', rowId, 'Should show:', self._search.filteredIds.includes(rowId));
|
||||
|
||||
if (rowId && self._search.filteredIds.includes(rowId)) {
|
||||
if (visibleCount < limit) {
|
||||
$row.show();
|
||||
visibleCount++;
|
||||
foundMatch = true;
|
||||
} else {
|
||||
$row.hide();
|
||||
foundAnyMatch = true;
|
||||
console.log('Showing row', index, 'with ID', rowId);
|
||||
}
|
||||
} else {
|
||||
$row.hide();
|
||||
}
|
||||
});
|
||||
|
||||
return foundMatch;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear table body
|
||||
*/
|
||||
_clearTableBody: function() {
|
||||
this.$('tbody .o_data_row').remove();
|
||||
this.$('.oe_no_results').remove();
|
||||
},
|
||||
|
||||
/**
|
||||
* Render filtered rows
|
||||
*/
|
||||
_renderFilteredRows: function() {
|
||||
var self = this;
|
||||
console.log('DOM filtering result - Found matches:', foundAnyMatch, 'Visible count:', visibleCount);
|
||||
|
||||
try {
|
||||
// Try Odoo's internal render methods
|
||||
if (typeof this._renderRows === 'function') {
|
||||
var $rows = this._renderRows();
|
||||
if ($rows && $rows.length) {
|
||||
this.$('tbody').append($rows);
|
||||
this._updatePager(this._search.filteredIds.length);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof this._renderBody === 'function') {
|
||||
var result = this._renderBody();
|
||||
if (result && typeof result.then === 'function') {
|
||||
result.then(function() {
|
||||
self._updatePager(self._search.filteredIds.length);
|
||||
});
|
||||
} else {
|
||||
this._updatePager(this._search.filteredIds.length);
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Render failed, using fallback:', err);
|
||||
// If no matches found in DOM, we need to re-render
|
||||
return foundAnyMatch;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update state and re-render - ENHANCED METHOD
|
||||
*/
|
||||
_updateStateAndRerender: function(records) {
|
||||
var self = this;
|
||||
var limit = this.state.limit || 80;
|
||||
var pageRecords = records.slice(0, limit);
|
||||
|
||||
console.log('Re-rendering with filtered data. Total records:', records.length, 'Page records:', pageRecords.length);
|
||||
|
||||
// Update state
|
||||
if (this.state && this.state.data) {
|
||||
this.state.data.records = pageRecords;
|
||||
this.state.count = records.length;
|
||||
this.state.res_ids = this._search.filteredIds;
|
||||
}
|
||||
|
||||
// Fallback: Create rows manually
|
||||
this._createRowsManually();
|
||||
// Clear existing rows
|
||||
this.$('tbody .o_data_row').remove();
|
||||
this.$('.oe_no_results').remove();
|
||||
|
||||
// Try different rendering approaches
|
||||
this._tryMultipleRenderMethods(pageRecords);
|
||||
|
||||
// Update pager
|
||||
this._updatePager(records.length);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create rows manually as last resort
|
||||
* Try multiple rendering methods - NEW ENHANCED METHOD
|
||||
*/
|
||||
_createRowsManually: function() {
|
||||
_tryMultipleRenderMethods: function(records) {
|
||||
var self = this;
|
||||
var records = this.state.data.records;
|
||||
var renderSuccess = false;
|
||||
|
||||
if (!records || records.length === 0) return;
|
||||
// Method 1: Try Odoo's _renderRows if available
|
||||
if (!renderSuccess && typeof this._renderRows === 'function') {
|
||||
try {
|
||||
var $rows = this._renderRows();
|
||||
if ($rows && $rows.length > 0) {
|
||||
this.$('tbody').append($rows);
|
||||
renderSuccess = true;
|
||||
console.log('Render method 1 succeeded');
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Render method 1 failed:', err);
|
||||
}
|
||||
}
|
||||
|
||||
records.forEach(function(record) {
|
||||
// Method 2: Try _renderBody if available
|
||||
if (!renderSuccess && typeof this._renderBody === 'function') {
|
||||
try {
|
||||
var result = this._renderBody();
|
||||
if (result) {
|
||||
if (typeof result.then === 'function') {
|
||||
result.then(function() {
|
||||
console.log('Render method 2 succeeded (async)');
|
||||
});
|
||||
} else {
|
||||
console.log('Render method 2 succeeded (sync)');
|
||||
}
|
||||
renderSuccess = true;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Render method 2 failed:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// Method 3: Manual row creation (fallback)
|
||||
if (!renderSuccess) {
|
||||
console.log('Using manual row creation fallback');
|
||||
this._createRowsManually(records);
|
||||
renderSuccess = true;
|
||||
}
|
||||
|
||||
return renderSuccess;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create rows manually - ENHANCED FALLBACK
|
||||
*/
|
||||
_createRowsManually: function(records) {
|
||||
var self = this;
|
||||
|
||||
if (!records || records.length === 0) {
|
||||
console.log('No records to create rows for');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Creating', records.length, 'rows manually');
|
||||
|
||||
records.forEach(function(record, index) {
|
||||
var $row = $('<tr class="o_data_row"></tr>');
|
||||
$row.data('id', record.id);
|
||||
$row.attr('data-id', record.id);
|
||||
|
||||
// Add cells for each visible column
|
||||
if (self.columns) {
|
||||
// Set multiple ID attributes for better compatibility
|
||||
$row.attr('data-id', record.id);
|
||||
$row.attr('data-res-id', record.id);
|
||||
$row.data('id', record.id);
|
||||
$row.data('res-id', record.id);
|
||||
|
||||
// Store record data
|
||||
$row.data('record', record);
|
||||
|
||||
console.log('Creating row for record ID:', record.id);
|
||||
|
||||
// Add cells based on visible columns
|
||||
if (self.columns && self.columns.length > 0) {
|
||||
self.columns.forEach(function(col) {
|
||||
if (!col.invisible && col.attrs && col.attrs.name) {
|
||||
var value = record[col.attrs.name] || '';
|
||||
|
||||
// Handle many2one fields
|
||||
if (typeof value === 'object' && value) {
|
||||
if (Array.isArray(value) && value.length >= 2) {
|
||||
value = value[1];
|
||||
} else if (value.display_name) {
|
||||
value = value.display_name;
|
||||
} else {
|
||||
value = '';
|
||||
}
|
||||
}
|
||||
|
||||
var $cell = $('<td class="o_data_cell"></td>');
|
||||
$cell.text(value);
|
||||
$cell.attr('data-field', col.attrs.name);
|
||||
var $cell = self._createCellForField(record, col);
|
||||
$row.append($cell);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Fallback: create basic cells
|
||||
var cellValue = record.display_name || record.name || record.id;
|
||||
var $cell = $('<td class="o_data_cell"></td>').text(cellValue);
|
||||
$row.append($cell);
|
||||
}
|
||||
|
||||
self.$('tbody').append($row);
|
||||
});
|
||||
|
||||
this._updatePager(this._search.filteredIds.length);
|
||||
console.log('Manual row creation completed');
|
||||
},
|
||||
|
||||
/**
|
||||
* Apply DOM filter (used after re-render)
|
||||
* Create cell for specific field - NEW HELPER METHOD
|
||||
*/
|
||||
_applyDOMFilter: function(filteredIds) {
|
||||
var self = this;
|
||||
var $rows = this.$('.o_data_row');
|
||||
var limit = this.state.limit || 80;
|
||||
var visibleCount = 0;
|
||||
_createCellForField: function(record, col) {
|
||||
var fieldName = col.attrs.name;
|
||||
var value = record[fieldName];
|
||||
var displayValue = '';
|
||||
|
||||
$rows.each(function() {
|
||||
var $row = $(this);
|
||||
var rowId = self._getRowId($row);
|
||||
|
||||
if (rowId && filteredIds.includes(rowId) && visibleCount < limit) {
|
||||
$row.show();
|
||||
visibleCount++;
|
||||
// Handle different field types
|
||||
if (value !== null && value !== undefined) {
|
||||
if (typeof value === 'object' && value) {
|
||||
// Many2one field
|
||||
if (Array.isArray(value) && value.length >= 2) {
|
||||
displayValue = value[1];
|
||||
} else if (value.display_name) {
|
||||
displayValue = value.display_name;
|
||||
} else {
|
||||
displayValue = String(value);
|
||||
}
|
||||
} else {
|
||||
$row.hide();
|
||||
displayValue = String(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var $cell = $('<td class="o_data_cell"></td>');
|
||||
$cell.text(displayValue);
|
||||
$cell.attr('data-field', fieldName);
|
||||
|
||||
// Add field-specific classes if needed
|
||||
if (col.attrs.class) {
|
||||
$cell.addClass(col.attrs.class);
|
||||
}
|
||||
|
||||
return $cell;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get row ID from DOM element
|
||||
* Get row ID from DOM element - ENHANCED METHOD
|
||||
*/
|
||||
_getRowId: function($row) {
|
||||
// Method 1: data-id attribute
|
||||
var id = $row.attr('data-id');
|
||||
if (id) return parseInt(id);
|
||||
if (id && !isNaN(parseInt(id))) {
|
||||
return parseInt(id);
|
||||
}
|
||||
|
||||
// Method 2: jQuery data
|
||||
id = $row.data('id');
|
||||
if (id) return parseInt(id);
|
||||
|
||||
// Method 3: data-res-id attribute
|
||||
// Method 2: data-res-id attribute
|
||||
id = $row.attr('data-res-id');
|
||||
if (id) return parseInt(id);
|
||||
if (id && !isNaN(parseInt(id))) {
|
||||
return parseInt(id);
|
||||
}
|
||||
|
||||
// Method 3: jQuery data id
|
||||
id = $row.data('id');
|
||||
if (id && !isNaN(parseInt(id))) {
|
||||
return parseInt(id);
|
||||
}
|
||||
|
||||
// Method 4: jQuery data res-id
|
||||
id = $row.data('res-id');
|
||||
if (id) return parseInt(id);
|
||||
|
||||
// Method 5: Check for record object
|
||||
var record = $row.data('record');
|
||||
if (record) {
|
||||
if (record.res_id) return record.res_id;
|
||||
if (record.id) return record.id;
|
||||
if (record.data && record.data.id) return record.data.id;
|
||||
if (id && !isNaN(parseInt(id))) {
|
||||
return parseInt(id);
|
||||
}
|
||||
|
||||
// Method 5: Check for record object in data
|
||||
var record = $row.data('record');
|
||||
if (record) {
|
||||
if (record.res_id && !isNaN(parseInt(record.res_id))) {
|
||||
return parseInt(record.res_id);
|
||||
}
|
||||
if (record.id && !isNaN(parseInt(record.id))) {
|
||||
return parseInt(record.id);
|
||||
}
|
||||
if (record.data && record.data.id && !isNaN(parseInt(record.data.id))) {
|
||||
return parseInt(record.data.id);
|
||||
}
|
||||
}
|
||||
|
||||
// Method 6: Look for hidden inputs with record ID
|
||||
var $hiddenInput = $row.find('input[name="id"]');
|
||||
if ($hiddenInput.length) {
|
||||
id = $hiddenInput.val();
|
||||
if (id && !isNaN(parseInt(id))) {
|
||||
return parseInt(id);
|
||||
}
|
||||
}
|
||||
|
||||
// Method 7: Check if row has any reference to record ID in classes
|
||||
var classes = $row.attr('class') || '';
|
||||
var match = classes.match(/o_data_row_(\d+)/);
|
||||
if (match) {
|
||||
return parseInt(match[1]);
|
||||
}
|
||||
|
||||
console.warn('Could not determine row ID for row:', $row[0]);
|
||||
return null;
|
||||
},
|
||||
|
||||
|
|
@ -487,7 +553,7 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
|
|||
},
|
||||
|
||||
/**
|
||||
* Client-side search fallback
|
||||
* Client-side search fallback - ENHANCED
|
||||
*/
|
||||
_clientSideSearch: function(value) {
|
||||
var self = this;
|
||||
|
|
@ -497,17 +563,25 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
|
|||
var limit = this.state.limit || 80;
|
||||
var matchedIds = [];
|
||||
|
||||
console.log('Using client-side search fallback');
|
||||
|
||||
this.$('.o_data_row').each(function() {
|
||||
var $row = $(this);
|
||||
var text = $row.text().toLowerCase();
|
||||
var normalizedText = self._normalizeArabic(text);
|
||||
var match = text.includes(searchLower) || normalizedText.includes(normalized);
|
||||
|
||||
if (match && visible < limit) {
|
||||
$row.show();
|
||||
visible++;
|
||||
var rowId = self._getRowId($row);
|
||||
if (rowId) matchedIds.push(rowId);
|
||||
if (match) {
|
||||
if (visible < limit) {
|
||||
$row.show();
|
||||
visible++;
|
||||
var rowId = self._getRowId($row);
|
||||
if (rowId) {
|
||||
matchedIds.push(rowId);
|
||||
}
|
||||
} else {
|
||||
$row.hide();
|
||||
}
|
||||
} else {
|
||||
$row.hide();
|
||||
}
|
||||
|
|
@ -528,12 +602,16 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
|
|||
}
|
||||
|
||||
this._updatePager(visible);
|
||||
|
||||
console.log('Client-side search completed. Visible:', visible, 'IDs:', matchedIds);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear search
|
||||
* Clear search - ENHANCED
|
||||
*/
|
||||
_clearSearch: function() {
|
||||
console.log('Clearing search');
|
||||
|
||||
// Clear UI
|
||||
this.$('.oe_search_input').val('');
|
||||
this.$('.oe_clear_search').hide();
|
||||
|
|
@ -574,18 +652,23 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
|
|||
this._search.originalData = null;
|
||||
this._search.originalDomain = null;
|
||||
} else {
|
||||
// Just show rows with limit
|
||||
// Just show all rows with limit
|
||||
var $rows = this.$('.o_data_row');
|
||||
var limit = this.state.limit || 80;
|
||||
var limit = this.state ? (this.state.limit || 80) : 80;
|
||||
|
||||
$rows.show(); // Show all first
|
||||
$rows.each(function(index) {
|
||||
$(this).toggle(index < limit);
|
||||
if (index >= limit) {
|
||||
$(this).hide();
|
||||
}
|
||||
});
|
||||
|
||||
if (this.state) {
|
||||
this._updatePager(this.state.count || $rows.length);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Search cleared');
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -602,6 +685,8 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
|
|||
offset: this.state.offset || 0
|
||||
};
|
||||
this._search.originalDomain = this.state.domain ? this.state.domain.slice() : [];
|
||||
|
||||
console.log('Stored original data:', this._search.originalData);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue