Complete rewrite with smart state management - search state persists across renders

This commit is contained in:
Mohamed Eltayar 2025-08-29 20:22:12 +03:00
parent 4e28b9d652
commit 38e321a734
1 changed files with 127 additions and 92 deletions

View File

@ -20,6 +20,8 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
this._super.apply(this, arguments);
// Initialize mutex for preventing concurrent operations
this._searchMutex = new concurrency.Mutex();
// Initialize search state early
this._initSearchState();
},
/**
@ -28,41 +30,27 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
_renderView: function () {
var self = this;
// Save search state before render
var savedSearchValue = '';
var savedFilteredIds = null;
if (this._search) {
savedSearchValue = this._search.value || '';
savedFilteredIds = this._search.filteredIds;
}
// IMPORTANT: Don't reset search state, just preserve it
var hasActiveSearch = this._search && this._search.value;
return this._super.apply(this, arguments).then(function () {
// Add search box if needed
if (self.arch && self.arch.tag === 'tree' &&
self.$el && self.$el.hasClass('o_list_view') &&
!self.$el.find('.oe_search_input').length) {
self.$el && self.$el.hasClass('o_list_view')) {
self._addSearchBox();
self._initSearchState();
}
// Restore search state if existed
if (savedSearchValue) {
if (!self._search) {
self._initSearchState();
// Add search box if not exists
if (!self.$el.find('.oe_search_input').length) {
self._addSearchBox();
}
self._search.value = savedSearchValue;
self._search.filteredIds = savedFilteredIds;
// Restore UI
self.$('.oe_search_input').val(savedSearchValue);
self.$('.oe_clear_search').show();
// Always restore search state after render
self._restoreSearchUI();
// Re-apply filter if we had filtered results
if (savedFilteredIds && savedFilteredIds.length > 0) {
// Use timeout to ensure DOM is ready
// If we have an active search, apply the filter
if (hasActiveSearch && self._search.filteredIds) {
// Apply filter after DOM is ready
setTimeout(function() {
self._applyDOMFilter(savedFilteredIds);
self._reapplySearchFilter();
}, 0);
}
}
@ -87,7 +75,7 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
},
/**
* Initialize search state
* Initialize search state (only once)
*/
_initSearchState: function() {
if (!this._search) {
@ -98,19 +86,54 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
originalData: null,
originalDomain: null,
filteredIds: null,
allFilteredRecords: null, // Store all filtered records
isFiltered: false
allFilteredRecords: null,
isFiltered: false,
lastSearchPromise: null
};
}
},
/**
* Restore search UI after render
*/
_restoreSearchUI: function() {
if (!this._search) return;
var $input = this.$('.oe_search_input');
var $clearBtn = this.$('.oe_clear_search');
var $count = this.$('.oe_search_count');
// Restore input value
if ($input.length && this._search.value) {
$input.val(this._search.value);
$clearBtn.show();
}
// Restore count if filtered
if (this._search.isFiltered && this._search.filteredIds) {
$count.text(_t('Found: ') + this._search.filteredIds.length + _t(' records')).show();
}
},
/**
* 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)
*/
_onSearchInput: function(e) {
if (!this._search) {
this._initSearchState();
}
this._search.value = $(e.currentTarget).val().trim();
},
@ -121,10 +144,6 @@ 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);
@ -194,7 +213,7 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
var fields = self._getReadFields();
// Execute search
return self._rpc({
var searchPromise = self._rpc({
model: self.state.model,
method: 'search_read',
args: [domain],
@ -203,13 +222,22 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
limit: false, // Get all records for accurate count
context: self.state.context || {}
}
}).then(function(results) {
// Process and display results
self._processSearchResults(results);
});
// Store promise to check if still valid later
self._search.lastSearchPromise = searchPromise;
return searchPromise.then(function(results) {
// Only process if this is still the latest search
if (self._search.lastSearchPromise === searchPromise) {
self._processSearchResults(results);
}
}).catch(function(error) {
console.error('Search failed:', error);
// Fallback to client-side search
self._clientSideSearch(value);
if (self._search.lastSearchPromise === searchPromise) {
self._clientSideSearch(value);
}
}).finally(function() {
self._showLoading(false);
});
@ -220,7 +248,6 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
* Process search results from server
*/
_processSearchResults: function(records) {
var self = this;
var count = records.length;
// Update search state
@ -236,36 +263,38 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
return;
}
// IMPORTANT: Update the view with filtered results
this._updateViewWithFilteredData(records);
// Update the view with filtered results
this._updateViewWithFilteredData(records, true);
},
/**
* Update view with filtered data - THE KEY METHOD
*/
_updateViewWithFilteredData: function(records) {
_updateViewWithFilteredData: function(records, updateState) {
var self = this;
var limit = this.state.limit || 80;
var pageRecords = records.slice(0, limit);
// Method 1: Try DOM filtering first (preserves everything)
if (this._tryDOMFiltering(this._search.filteredIds, limit)) {
return; // Success with DOM filtering
// Method 1: Try DOM filtering first (fastest, preserves input)
var domFilterSuccess = this._tryDOMFiltering(this._search.filteredIds, limit);
if (domFilterSuccess) {
// DOM filtering worked, just update pager
this._updatePager(records.length);
return;
}
// Method 2: Update state and trigger re-render
if (this.state && this.state.data) {
// 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 existing rows
this.$('tbody .o_data_row').remove();
this.$('.oe_no_results').remove();
// Re-render rows using Odoo's method
this._renderRowsForFilteredData();
// Clear and re-render
this._clearTableBody();
this._renderFilteredRows();
}
},
@ -290,31 +319,34 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
if (visibleCount < limit) {
$row.show();
visibleCount++;
foundMatch = true;
} else {
$row.hide();
}
foundMatch = true;
} else {
$row.hide();
}
});
// Update pager if we found matches
if (foundMatch) {
this._updatePager(filteredIds.length);
}
return foundMatch;
},
/**
* Render rows for filtered data
* Clear table body
*/
_renderRowsForFilteredData: function() {
_clearTableBody: function() {
this.$('tbody .o_data_row').remove();
this.$('.oe_no_results').remove();
},
/**
* Render filtered rows
*/
_renderFilteredRows: function() {
var self = this;
try {
// Method 1: Use _renderRows if available
// Try Odoo's internal render methods
if (typeof this._renderRows === 'function') {
var $rows = this._renderRows();
if ($rows && $rows.length) {
@ -324,7 +356,6 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
}
}
// Method 2: Use _renderBody
if (typeof this._renderBody === 'function') {
var result = this._renderBody();
if (result && typeof result.then === 'function') {
@ -351,9 +382,12 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
var self = this;
var records = this.state.data.records;
if (!records || records.length === 0) return;
records.forEach(function(record) {
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) {
@ -363,7 +397,13 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
// Handle many2one fields
if (typeof value === 'object' && value) {
value = value[1] || value.display_name || '';
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>');
@ -400,13 +440,6 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
$row.hide();
}
});
// Update count display
if (this._search && this._search.filteredIds) {
this.$('.oe_search_count')
.text(_t('Found: ') + this._search.filteredIds.length + _t(' records'))
.show();
}
},
/**
@ -414,14 +447,22 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
*/
_getRowId: function($row) {
// Method 1: data-id attribute
var id = $row.data('id');
var id = $row.attr('data-id');
if (id) return parseInt(id);
// Method 2: data-res-id attribute
// Method 2: jQuery data
id = $row.data('id');
if (id) return parseInt(id);
// Method 3: data-res-id attribute
id = $row.attr('data-res-id');
if (id) return parseInt(id);
// Method 4: jQuery data res-id
id = $row.data('res-id');
if (id) return parseInt(id);
// Method 3: Check for record object
// Method 5: Check for record object
var record = $row.data('record');
if (record) {
if (record.res_id) return record.res_id;
@ -429,12 +470,6 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
if (record.data && record.data.id) return record.data.id;
}
// Method 4: dataset
if ($row[0] && $row[0].dataset) {
if ($row[0].dataset.id) return parseInt($row[0].dataset.id);
if ($row[0].dataset.resId) return parseInt($row[0].dataset.resId);
}
return null;
},
@ -455,6 +490,7 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
* Client-side search fallback
*/
_clientSideSearch: function(value) {
var self = this;
var searchLower = value.toLowerCase();
var normalized = this._normalizeArabic(searchLower);
var visible = 0;
@ -464,18 +500,18 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
this.$('.o_data_row').each(function() {
var $row = $(this);
var text = $row.text().toLowerCase();
var normalizedText = this._normalizeArabic ? this._normalizeArabic(text) : text;
var normalizedText = self._normalizeArabic(text);
var match = text.includes(searchLower) || normalizedText.includes(normalized);
if (match && visible < limit) {
$row.show();
visible++;
var rowId = parseInt($row.data('id') || $row.data('res-id'));
var rowId = self._getRowId($row);
if (rowId) matchedIds.push(rowId);
} else {
$row.hide();
}
}.bind(this));
});
// Update state
this._search.isFiltered = true;
@ -498,19 +534,18 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
* Clear search
*/
_clearSearch: function() {
var self = this;
// Clear UI
this.$('.oe_search_input').val('');
this.$('.oe_clear_search').hide();
this.$('.oe_search_count').hide();
this.$('.oe_no_results').remove();
// Clear state
// Clear search state (but keep structure)
this._search.value = '';
this._search.filteredIds = null;
this._search.allFilteredRecords = null;
this._search.isFiltered = false;
this._search.lastSearchPromise = null;
// Restore original data
if (this._search.originalData) {
@ -535,7 +570,7 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
// Restore pager
this._updatePager(this._search.originalData.count);
// Clear stored data
// Clear stored original data
this._search.originalData = null;
this._search.originalDomain = null;
} else {
@ -554,10 +589,10 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
},
/**
* Store original data
* Store original data (only once per search session)
*/
_storeOriginalData: function() {
if (this.state) {
if (this.state && !this._search.originalData) {
this._search.originalData = {
records: this.state.data && this.state.data.records ?
this.state.data.records.slice() : [],