From 7a906739e3cd3e3cd2f3914e2712d063b163f20a Mon Sep 17 00:00:00 2001
From: Mohamed Eltayar <152964073+maltayyar2@users.noreply.github.com>
Date: Fri, 29 Aug 2025 18:44:44 +0300
Subject: [PATCH] Final fix: Prevent full view reload - Update body content
only to preserve search input
---
.../static/src/js/list_search.js | 412 ++++++------------
1 file changed, 145 insertions(+), 267 deletions(-)
diff --git a/odex25_base/fims_general_search_tree_view/static/src/js/list_search.js b/odex25_base/fims_general_search_tree_view/static/src/js/list_search.js
index f91536bce..c19a154cc 100644
--- a/odex25_base/fims_general_search_tree_view/static/src/js/list_search.js
+++ b/odex25_base/fims_general_search_tree_view/static/src/js/list_search.js
@@ -8,7 +8,7 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
ListRenderer.include({
events: _.extend({}, ListRenderer.prototype.events, {
'keyup .oe_search_input': '_onSearchKeyUp',
- 'input .oe_search_input': '_onSearchInput', // Add input event
+ 'input .oe_search_input': '_onSearchInput',
'click .oe_clear_search': '_onClearSearchClick'
}),
@@ -17,55 +17,46 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
*/
_renderView: function () {
var self = this;
+ // Store search value before render
+ var previousSearchValue = '';
+ if (this._search && this._search.value) {
+ previousSearchValue = this._search.value;
+ }
+
return this._super.apply(this, arguments).then(function () {
- // Only add search box to tree views
+ // Check if search box already exists and has a value
+ var existingInput = self.$el.find('.oe_search_input');
+ var hasExistingValue = existingInput.length && existingInput.val();
+
+ // Only add search box to tree views if not exists
if (self.arch && self.arch.tag === 'tree' &&
self.$el && self.$el.hasClass('o_list_view') &&
- !self.$el.find('.oe_search_input').length) {
+ !existingInput.length) {
self._addSearchBox();
self._initSearchState();
}
- // Restore search value if exists
- self._restoreSearchInput();
- });
- },
-
- /**
- * @override - Hook into render body to preserve search
- */
- _renderBody: function () {
- var searchValue = this._search ? this._search.value : '';
- var self = this;
-
- var result = this._super.apply(this, arguments);
-
- // Check if result is a Promise
- if (result && typeof result.then === 'function') {
- return result.then(function(res) {
- // Restore search value after render
- if (searchValue) {
- setTimeout(function() {
- self._restoreSearchInput();
- }, 0);
+
+ // Restore previous search value if we had one
+ if (previousSearchValue || hasExistingValue) {
+ if (!self._search) {
+ self._initSearchState();
}
- return res;
- });
- } else {
- // If not a promise, restore immediately
- if (searchValue) {
- setTimeout(function() {
- self._restoreSearchInput();
- }, 0);
+ self._search.value = previousSearchValue || hasExistingValue;
+ self._restoreSearchInput();
}
- return result;
- }
+ });
},
/**
* Add search box to view
*/
_addSearchBox: function() {
+ // Check if already exists
+ if (this.$el.find('.oe_search_container').length) {
+ return;
+ }
+
var html = '
' +
'' +
'' +
@@ -79,22 +70,26 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
* Initialize search state
*/
_initSearchState: function() {
- this._search = {
- timer: null,
- value: '',
- active: false,
- originalData: null,
- originalDomain: null,
- filteredIds: null,
- preserveInput: true // Flag to preserve input value
- };
+ if (!this._search) {
+ this._search = {
+ timer: null,
+ value: '',
+ active: false,
+ originalData: null,
+ originalDomain: null,
+ filteredIds: null,
+ isFiltered: false
+ };
+ }
},
/**
- * Handle search input event (for immediate value tracking)
+ * Handle search input event
*/
_onSearchInput: function(e) {
- // Store value immediately on any input
+ if (!this._search) {
+ this._initSearchState();
+ }
this._search.value = $(e.currentTarget).val().trim();
},
@@ -105,6 +100,10 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
var self = this;
var value = $(e.currentTarget).val().trim();
+ if (!this._search) {
+ this._initSearchState();
+ }
+
// Clear previous timer
if (this._search.timer) {
clearTimeout(this._search.timer);
@@ -118,7 +117,7 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
// Require at least 2 characters or empty to search
if (value.length === 1) {
- return; // Don't search for single character
+ return;
}
// Debounce search
@@ -157,13 +156,10 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
return;
}
- // Store original data and domain on first search
- if (!this._search.originalData) {
+ // Store original data on first search
+ if (!this._search.originalData && !this._search.isFiltered) {
this._storeOriginalData();
}
- if (!this._search.originalDomain) {
- this._search.originalDomain = this.state.domain || [];
- }
// Start search
this._search.active = true;
@@ -173,8 +169,9 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
var domain = this._buildSearchDomain(value);
// Add base domain if exists
- if (this._search.originalDomain && this._search.originalDomain.length > 0) {
- domain = ['&'].concat(this._search.originalDomain).concat(domain);
+ var baseDomain = this._search.originalDomain || this.state.domain || [];
+ if (baseDomain.length > 0) {
+ domain = ['&'].concat(baseDomain).concat(domain);
}
// Get fields to read
@@ -187,12 +184,11 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
args: [domain],
kwargs: {
fields: fields,
- limit: false, // Get all records for count
+ limit: false,
context: this.state.context || {}
}
}).then(function(results) {
- // Always preserve the search value
- self._search.preserveInput = true;
+ self._search.isFiltered = true;
self._displayResults(results);
}).catch(function(error) {
console.error('Search failed:', error);
@@ -200,10 +196,6 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
}).finally(function() {
self._search.active = false;
self._showLoading(false);
- // Always restore search value after operations
- setTimeout(function() {
- self._restoreSearchInput();
- }, 100);
});
},
@@ -233,12 +225,11 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
}
});
- // Build OR domain properly
+ // Build OR domain
if (conditions.length === 0) {
return [];
}
- // Always return as array
if (conditions.length === 1) {
return conditions;
}
@@ -256,9 +247,6 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
*/
_getSearchableFields: function() {
var fields = [];
- var self = this;
-
- // Get field definitions
var fieldDefs = this.state.fields || {};
// Get from visible columns first
@@ -315,154 +303,95 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
},
/**
- * Display search results
+ * Display search results - OPTIMIZED VERSION
*/
_displayResults: function(records) {
var self = this;
var count = records.length;
- // Show count - KEEP IT VISIBLE
+ // Show count
this.$('.oe_search_count').text(_t('Found: ') + count + _t(' records')).show();
if (count === 0) {
this._showNoResults();
- // Still preserve search input
- this._restoreSearchInput();
return;
}
- // Store filtered IDs for later use
+ // Store filtered IDs
this._search.filteredIds = records.map(function(r) { return r.id; });
- // Re-render the list with pagination
- this._renderFilteredRecordsWithPagination(records);
- },
-
- /**
- * Render filtered records with proper pagination
- */
- _renderFilteredRecordsWithPagination: function(records) {
- var self = this;
-
- // Get current limit (default 80)
+ // Get pagination settings
var limit = this.state.limit || 80;
var offset = 0;
- // Get IDs for filtered records
- var filteredIds = records.map(function(r) { return r.id; });
-
- // Create domain to filter by IDs
- var idDomain = [['id', 'in', filteredIds]];
-
- // Try to use parent's reload method with proper pagination
- if (this.getParent() && typeof this.getParent().reload === 'function') {
- try {
- // Update state temporarily
- this.state.domain = idDomain;
-
- // Store total count for pagination
- this.state.count = records.length;
-
- // Reload with pagination settings
- this.getParent().reload({
- domain: idDomain,
- context: this.state.context,
- limit: limit,
- offset: offset
- }).then(function() {
- // Force restore search input and count after reload
- setTimeout(function() {
- self._forceRestoreSearchState();
- self.$('.oe_search_count').text(_t('Found: ') + records.length + _t(' records')).show();
- }, 100);
- }).catch(function(err) {
- console.warn('Parent reload failed:', err);
- self._renderRecordsWithPagination(records, limit, offset);
- });
-
- return;
- } catch (err) {
- console.warn('Reload method failed:', err);
- }
- }
-
- // Fallback: render with pagination
- this._renderRecordsWithPagination(records, limit, offset);
- },
-
- /**
- * Render records with pagination (fallback)
- */
- _renderRecordsWithPagination: function(records, limit, offset) {
- var self = this;
-
// Get records for current page
var pageRecords = records.slice(offset, offset + limit);
- // Update state
- if (this.state && this.state.data) {
- this.state.data.records = pageRecords;
- this.state.count = records.length;
- this.state.res_ids = pageRecords.map(function(r) { return r.id; });
+ // Update state WITHOUT triggering full reload
+ if (this.state) {
+ // Update data
+ if (this.state.data) {
+ this.state.data.records = pageRecords;
+ }
+ this.state.count = count;
+ this.state.res_ids = this._search.filteredIds;
+
+ // Update domain for pagination to work
+ this.state.domain = [['id', 'in', this._search.filteredIds]];
}
- // Try to render using _renderBody
- if (typeof this._renderBody === 'function') {
+ // Render only the body content
+ this._renderBodyContent(pageRecords);
+ },
+
+ /**
+ * Render body content without full reload
+ */
+ _renderBodyContent: function(records) {
+ var self = this;
+
+ // Remove existing rows
+ this.$('tbody .o_data_row').remove();
+ this.$('.oe_no_results').remove();
+
+ // Check if we have _renderRows method (Odoo 14)
+ if (typeof this._renderRows === 'function') {
try {
- // Remove existing rows
- this.$('tbody .o_data_row').remove();
- this.$('.oe_no_results').remove();
-
- // Render new rows
+ var $rows = this._renderRows();
+ this.$('tbody').append($rows);
+ } catch (err) {
+ console.warn('_renderRows failed:', err);
+ this._fallbackRender(records);
+ }
+ } else if (typeof this._renderBody === 'function') {
+ try {
+ // Call _renderBody but handle result properly
var result = this._renderBody();
- // Handle both promise and non-promise returns
+ // Handle promise if returned
if (result && typeof result.then === 'function') {
result.then(function() {
- self._forceRestoreSearchState();
- self.$('.oe_search_count').text(_t('Found: ') + records.length + _t(' records')).show();
+ // Body rendered
});
- } else {
- self._forceRestoreSearchState();
- self.$('.oe_search_count').text(_t('Found: ') + records.length + _t(' records')).show();
}
} catch (err) {
- console.error('Render error:', err);
- this._fallbackRender(pageRecords);
+ console.warn('_renderBody failed:', err);
+ this._fallbackRender(records);
}
} else {
- this._fallbackRender(pageRecords);
+ this._fallbackRender(records);
}
- },
-
- /**
- * Force restore search state (input value and UI)
- */
- _forceRestoreSearchState: function() {
- if (this._search && this._search.value && this._search.preserveInput) {
- var $input = this.$('.oe_search_input');
- if ($input.length && $input.val() !== this._search.value) {
- $input.val(this._search.value);
- $input.focus(); // Keep focus on input
- // Set cursor position to end
- var input = $input[0];
- if (input.setSelectionRange) {
- var len = this._search.value.length;
- input.setSelectionRange(len, len);
- }
- }
- this.$('.oe_clear_search').show();
+
+ // Update pager if exists
+ if (this.getParent() && this.getParent().pager) {
+ this.getParent().pager.updateState({
+ current_min: 1,
+ size: this.state.count,
+ limit: this.state.limit
+ });
}
},
- /**
- * Restore search input value
- */
- _restoreSearchInput: function() {
- // Use force restore for consistency
- this._forceRestoreSearchState();
- },
-
/**
* Fallback render using DOM manipulation
*/
@@ -481,7 +410,7 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
// Hide all rows first
$rows.hide();
- // Show matching rows (limited to page size)
+ // Show matching rows
var shown = 0;
var limit = this.state.limit || 80;
@@ -497,12 +426,9 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
}
});
- // Show message if no visible rows
if (shown === 0) {
this._showNoResults();
}
-
- this._forceRestoreSearchState();
},
/**
@@ -521,23 +447,11 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
if (record.data && record.data.id) return record.data.id;
}
- // Method 3: Look for Odoo's internal data
+ // Method 3: Odoo's internal data
if ($row[0] && $row[0].dataset) {
if ($row[0].dataset.id) return parseInt($row[0].dataset.id);
}
- // Method 4: Check first cell for ID field
- var $idCell = $row.find('td[data-field="id"]');
- if ($idCell.length) {
- var idText = $idCell.text().trim();
- if (idText && !isNaN(idText)) return parseInt(idText);
- }
-
- // Method 5: Extract from class
- var classes = $row.attr('class') || '';
- var match = classes.match(/o_data_row_(\d+)/);
- if (match) return parseInt(match[1]);
-
return null;
},
@@ -573,17 +487,12 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
if (visible === 0) {
this._showNoResults();
}
-
- this._forceRestoreSearchState();
},
/**
* Clear search and restore original view
*/
_clearSearch: function() {
- // Don't preserve input when clearing
- this._search.preserveInput = false;
-
// Clear UI
this.$('.oe_search_input').val('');
this.$('.oe_clear_search').hide();
@@ -593,17 +502,13 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
// Clear state
this._search.value = '';
this._search.filteredIds = null;
+ this._search.isFiltered = false;
- // Restore original domain
- if (this._search.originalDomain !== null) {
- this.state.domain = this._search.originalDomain;
- }
-
- // Restore original data with proper pagination
+ // Restore original data
if (this._search.originalData) {
- this._restoreOriginalDataWithPagination();
+ this._restoreOriginalData();
} else {
- // Just show all rows respecting pagination
+ // Show all rows with pagination
var $rows = this.$('.o_data_row');
var limit = this.state.limit || 80;
@@ -611,11 +516,6 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
$(this).toggle(index < limit);
});
}
-
- // Re-enable preserve for future searches
- setTimeout(function() {
- this._search.preserveInput = true;
- }.bind(this), 100);
},
/**
@@ -631,85 +531,63 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
limit: this.state.limit,
offset: this.state.offset || 0
};
+ this._search.originalDomain = this.state.domain ? this.state.domain.slice() : [];
}
},
/**
- * Restore original data with pagination
+ * Restore original data
*/
- _restoreOriginalDataWithPagination: function() {
+ _restoreOriginalData: function() {
if (!this._search.originalData) return;
- var self = this;
var orig = this._search.originalData;
// Restore state
if (this.state) {
- this.state.domain = this._search.originalDomain || [];
- this.state.count = orig.count;
- this.state.limit = orig.limit;
- this.state.offset = orig.offset;
- }
-
- // Try parent reload for clean restore
- if (this.getParent() && typeof this.getParent().reload === 'function') {
- this.getParent().reload({
- domain: this._search.originalDomain || [],
- context: this.state.context,
- limit: orig.limit,
- offset: orig.offset
- }).then(function() {
- // Clear search data
- self._search.originalData = null;
- self._search.originalDomain = null;
- }).catch(function() {
- // Fallback
- self._restoreOriginalRecords(orig);
- });
- } else {
- this._restoreOriginalRecords(orig);
- }
- },
-
- /**
- * Restore original records (fallback)
- */
- _restoreOriginalRecords: function(orig) {
- // Update state
- if (this.state && this.state.data) {
- this.state.data.records = orig.records;
+ if (this.state.data) {
+ this.state.data.records = orig.records;
+ }
this.state.count = orig.count;
this.state.res_ids = orig.res_ids;
+ this.state.limit = orig.limit;
+ this.state.offset = orig.offset;
+ this.state.domain = this._search.originalDomain || [];
}
- // Re-render
- if (typeof this._renderBody === 'function') {
- var result = this._renderBody();
- // Handle both promise and non-promise
- if (!result || typeof result.then !== 'function') {
- // Not a promise, already rendered
- }
- } else {
- // Show original rows with pagination
- var $rows = this.$('.o_data_row');
- var limit = orig.limit || 80;
-
- $rows.each(function(index) {
- $(this).toggle(index < limit);
- });
- }
+ // Render the restored data
+ this._renderBodyContent(orig.records);
// Clear stored data
this._search.originalData = null;
this._search.originalDomain = null;
},
+ /**
+ * Restore search input value
+ */
+ _restoreSearchInput: function() {
+ if (this._search && this._search.value) {
+ var $input = this.$('.oe_search_input');
+ if ($input.length && $input.val() !== this._search.value) {
+ $input.val(this._search.value);
+ }
+ this.$('.oe_clear_search').toggle(!!this._search.value);
+
+ // Also restore count if we have filtered results
+ if (this._search.isFiltered && this._search.filteredIds) {
+ this.$('.oe_search_count')
+ .text(_t('Found: ') + this._search.filteredIds.length + _t(' records'))
+ .show();
+ }
+ }
+ },
+
/**
* Show loading state
*/
_showLoading: function(show) {
this.$('.oe_search_loading').toggle(show);
- // Don't hide count when loading
},
/**
@@ -734,7 +612,7 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
},
/**
- * Normalize Arabic text for better search
+ * Normalize Arabic text
*/
_normalizeArabic: function(text) {
if (!text) return '';