Final fix: Prevent full view reload - Update body content only to preserve search input

This commit is contained in:
Mohamed Eltayar 2025-08-29 18:44:44 +03:00
parent 7146c96da3
commit 7a906739e3
1 changed files with 145 additions and 267 deletions

View File

@ -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 = '<div class="oe_search_container" style="display: flex; align-items: center; margin: 8px; background: #f8f9fa; padding: 10px; border-radius: 4px;">' +
'<input type="text" class="oe_search_input" placeholder="' + _t('Search...') + '" style="flex: 1; border: 1px solid #ccc; height: 32px; padding: 0 12px; border-radius: 4px;">' +
'<button class="btn btn-sm btn-secondary oe_clear_search ml-2" style="display: none;">' + _t('Clear') + '</button>' +
@ -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 '';