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 '';