Complete rewrite with clear logic flow - DOM filtering first, then state update with re-render
This commit is contained in:
parent
059383f5fb
commit
4e28b9d652
|
|
@ -4,6 +4,7 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
|
|||
var ListRenderer = require('web.ListRenderer');
|
||||
var core = require('web.core');
|
||||
var _t = core._t;
|
||||
var concurrency = require('web.concurrency');
|
||||
|
||||
ListRenderer.include({
|
||||
events: _.extend({}, ListRenderer.prototype.events, {
|
||||
|
|
@ -12,44 +13,57 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
|
|||
'click .oe_clear_search': '_onClearSearchClick'
|
||||
}),
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
// Initialize mutex for preventing concurrent operations
|
||||
this._searchMutex = new concurrency.Mutex();
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_renderView: function () {
|
||||
var self = this;
|
||||
// Store search value before render
|
||||
var previousSearchValue = '';
|
||||
var previousFilteredIds = null;
|
||||
|
||||
// Save search state before render
|
||||
var savedSearchValue = '';
|
||||
var savedFilteredIds = null;
|
||||
if (this._search) {
|
||||
previousSearchValue = this._search.value || '';
|
||||
previousFilteredIds = this._search.filteredIds;
|
||||
savedSearchValue = this._search.value || '';
|
||||
savedFilteredIds = this._search.filteredIds;
|
||||
}
|
||||
|
||||
return this._super.apply(this, arguments).then(function () {
|
||||
// Check if search box already exists
|
||||
var existingInput = self.$el.find('.oe_search_input');
|
||||
|
||||
// Only add search box to tree views if not exists
|
||||
// Add search box if needed
|
||||
if (self.arch && self.arch.tag === 'tree' &&
|
||||
self.$el && self.$el.hasClass('o_list_view') &&
|
||||
!existingInput.length) {
|
||||
!self.$el.find('.oe_search_input').length) {
|
||||
|
||||
self._addSearchBox();
|
||||
self._initSearchState();
|
||||
}
|
||||
|
||||
// Restore previous search state if we had one
|
||||
if (previousSearchValue) {
|
||||
// Restore search state if existed
|
||||
if (savedSearchValue) {
|
||||
if (!self._search) {
|
||||
self._initSearchState();
|
||||
}
|
||||
self._search.value = previousSearchValue;
|
||||
self._search.filteredIds = previousFilteredIds;
|
||||
self._restoreSearchInput();
|
||||
self._search.value = savedSearchValue;
|
||||
self._search.filteredIds = savedFilteredIds;
|
||||
|
||||
// If we had filtered results, apply filter to new render
|
||||
if (previousFilteredIds && previousFilteredIds.length > 0) {
|
||||
self._applySearchFilter(previousFilteredIds);
|
||||
// Restore UI
|
||||
self.$('.oe_search_input').val(savedSearchValue);
|
||||
self.$('.oe_clear_search').show();
|
||||
|
||||
// Re-apply filter if we had filtered results
|
||||
if (savedFilteredIds && savedFilteredIds.length > 0) {
|
||||
// Use timeout to ensure DOM is ready
|
||||
setTimeout(function() {
|
||||
self._applyDOMFilter(savedFilteredIds);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -59,7 +73,6 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
|
|||
* Add search box to view
|
||||
*/
|
||||
_addSearchBox: function() {
|
||||
// Check if already exists
|
||||
if (this.$el.find('.oe_search_container').length) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -85,14 +98,14 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
|
|||
originalData: null,
|
||||
originalDomain: null,
|
||||
filteredIds: null,
|
||||
filteredRecords: null,
|
||||
allFilteredRecords: null, // Store all filtered records
|
||||
isFiltered: false
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle search input event
|
||||
* Handle search input event (immediate tracking)
|
||||
*/
|
||||
_onSearchInput: function(e) {
|
||||
if (!this._search) {
|
||||
|
|
@ -142,72 +155,421 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
|
|||
},
|
||||
|
||||
/**
|
||||
* Perform search
|
||||
* Perform search - THE MAIN SEARCH LOGIC
|
||||
*/
|
||||
_performSearch: function(value) {
|
||||
var self = this;
|
||||
|
||||
// Clear if empty
|
||||
if (!value) {
|
||||
this._clearSearch();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check prerequisites
|
||||
if (!this.state || !this.state.model) {
|
||||
console.error('Missing model information');
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent concurrent searches
|
||||
if (this._search.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Store original data on first search
|
||||
if (!this._search.originalData && !this._search.isFiltered) {
|
||||
this._storeOriginalData();
|
||||
}
|
||||
|
||||
// Start search
|
||||
this._search.active = true;
|
||||
this._showLoading(true);
|
||||
|
||||
// Build domain
|
||||
var domain = this._buildSearchDomain(value);
|
||||
|
||||
// Add base domain if exists
|
||||
var baseDomain = this._search.originalDomain || this.state.domain || [];
|
||||
if (baseDomain.length > 0) {
|
||||
domain = ['&'].concat(baseDomain).concat(domain);
|
||||
}
|
||||
|
||||
// Get fields to read
|
||||
var fields = this._getReadFields();
|
||||
|
||||
// Execute search
|
||||
this._rpc({
|
||||
model: this.state.model,
|
||||
method: 'search_read',
|
||||
args: [domain],
|
||||
kwargs: {
|
||||
fields: fields,
|
||||
limit: false,
|
||||
context: this.state.context || {}
|
||||
// Use mutex to prevent concurrent searches
|
||||
return this._searchMutex.exec(function() {
|
||||
// Clear if empty
|
||||
if (!value) {
|
||||
return self._clearSearch();
|
||||
}
|
||||
}).then(function(results) {
|
||||
self._search.isFiltered = true;
|
||||
self._search.filteredRecords = results;
|
||||
self._displayResults(results);
|
||||
}).catch(function(error) {
|
||||
console.error('Search failed:', error);
|
||||
self._clientSideSearch(value);
|
||||
}).finally(function() {
|
||||
self._search.active = false;
|
||||
self._showLoading(false);
|
||||
|
||||
// Check prerequisites
|
||||
if (!self.state || !self.state.model) {
|
||||
console.error('Missing model information');
|
||||
return;
|
||||
}
|
||||
|
||||
// Store original data on first search
|
||||
if (!self._search.originalData && !self._search.isFiltered) {
|
||||
self._storeOriginalData();
|
||||
}
|
||||
|
||||
// Show loading
|
||||
self._showLoading(true);
|
||||
|
||||
// Build domain
|
||||
var domain = self._buildSearchDomain(value);
|
||||
|
||||
// Add base domain if exists
|
||||
var baseDomain = self._search.originalDomain || self.state.domain || [];
|
||||
if (baseDomain.length > 0) {
|
||||
domain = ['&'].concat(baseDomain).concat(domain);
|
||||
}
|
||||
|
||||
// Get fields to read
|
||||
var fields = self._getReadFields();
|
||||
|
||||
// Execute search
|
||||
return self._rpc({
|
||||
model: self.state.model,
|
||||
method: 'search_read',
|
||||
args: [domain],
|
||||
kwargs: {
|
||||
fields: fields,
|
||||
limit: false, // Get all records for accurate count
|
||||
context: self.state.context || {}
|
||||
}
|
||||
}).then(function(results) {
|
||||
// Process and display results
|
||||
self._processSearchResults(results);
|
||||
}).catch(function(error) {
|
||||
console.error('Search failed:', error);
|
||||
// Fallback to client-side search
|
||||
self._clientSideSearch(value);
|
||||
}).finally(function() {
|
||||
self._showLoading(false);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Process search results from server
|
||||
*/
|
||||
_processSearchResults: function(records) {
|
||||
var self = this;
|
||||
var count = records.length;
|
||||
|
||||
// Update search state
|
||||
this._search.isFiltered = true;
|
||||
this._search.allFilteredRecords = records;
|
||||
this._search.filteredIds = records.map(function(r) { return r.id; });
|
||||
|
||||
// Show count
|
||||
this.$('.oe_search_count').text(_t('Found: ') + count + _t(' records')).show();
|
||||
|
||||
if (count === 0) {
|
||||
this._showNoResults();
|
||||
return;
|
||||
}
|
||||
|
||||
// IMPORTANT: Update the view with filtered results
|
||||
this._updateViewWithFilteredData(records);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update view with filtered data - THE KEY METHOD
|
||||
*/
|
||||
_updateViewWithFilteredData: function(records) {
|
||||
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 2: Update state and trigger re-render
|
||||
if (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();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Try DOM filtering first (fastest method)
|
||||
*/
|
||||
_tryDOMFiltering: function(filteredIds, limit) {
|
||||
var $rows = this.$('.o_data_row');
|
||||
if ($rows.length === 0) {
|
||||
return false; // No rows to filter
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var visibleCount = 0;
|
||||
var foundMatch = false;
|
||||
|
||||
$rows.each(function() {
|
||||
var $row = $(this);
|
||||
var rowId = self._getRowId($row);
|
||||
|
||||
if (rowId && filteredIds.includes(rowId)) {
|
||||
if (visibleCount < limit) {
|
||||
$row.show();
|
||||
visibleCount++;
|
||||
} 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
|
||||
*/
|
||||
_renderRowsForFilteredData: function() {
|
||||
var self = this;
|
||||
|
||||
try {
|
||||
// Method 1: Use _renderRows if available
|
||||
if (typeof this._renderRows === 'function') {
|
||||
var $rows = this._renderRows();
|
||||
if ($rows && $rows.length) {
|
||||
this.$('tbody').append($rows);
|
||||
this._updatePager(this._search.filteredIds.length);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Method 2: Use _renderBody
|
||||
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);
|
||||
}
|
||||
|
||||
// Fallback: Create rows manually
|
||||
this._createRowsManually();
|
||||
},
|
||||
|
||||
/**
|
||||
* Create rows manually as last resort
|
||||
*/
|
||||
_createRowsManually: function() {
|
||||
var self = this;
|
||||
var records = this.state.data.records;
|
||||
|
||||
records.forEach(function(record) {
|
||||
var $row = $('<tr class="o_data_row"></tr>');
|
||||
$row.data('id', record.id);
|
||||
|
||||
// Add cells for each visible column
|
||||
if (self.columns) {
|
||||
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) {
|
||||
value = value[1] || value.display_name || '';
|
||||
}
|
||||
|
||||
var $cell = $('<td class="o_data_cell"></td>');
|
||||
$cell.text(value);
|
||||
$cell.attr('data-field', col.attrs.name);
|
||||
$row.append($cell);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
self.$('tbody').append($row);
|
||||
});
|
||||
|
||||
this._updatePager(this._search.filteredIds.length);
|
||||
},
|
||||
|
||||
/**
|
||||
* Apply DOM filter (used after re-render)
|
||||
*/
|
||||
_applyDOMFilter: function(filteredIds) {
|
||||
var self = this;
|
||||
var $rows = this.$('.o_data_row');
|
||||
var limit = this.state.limit || 80;
|
||||
var visibleCount = 0;
|
||||
|
||||
$rows.each(function() {
|
||||
var $row = $(this);
|
||||
var rowId = self._getRowId($row);
|
||||
|
||||
if (rowId && filteredIds.includes(rowId) && visibleCount < limit) {
|
||||
$row.show();
|
||||
visibleCount++;
|
||||
} else {
|
||||
$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();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get row ID from DOM element
|
||||
*/
|
||||
_getRowId: function($row) {
|
||||
// Method 1: data-id attribute
|
||||
var id = $row.data('id');
|
||||
if (id) return parseInt(id);
|
||||
|
||||
// Method 2: data-res-id attribute
|
||||
id = $row.data('res-id');
|
||||
if (id) return parseInt(id);
|
||||
|
||||
// Method 3: 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update pager
|
||||
*/
|
||||
_updatePager: function(totalCount) {
|
||||
if (this.getParent() && this.getParent().pager) {
|
||||
this.getParent().pager.updateState({
|
||||
current_min: totalCount > 0 ? 1 : 0,
|
||||
size: totalCount,
|
||||
limit: this.state.limit || 80
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Client-side search fallback
|
||||
*/
|
||||
_clientSideSearch: function(value) {
|
||||
var searchLower = value.toLowerCase();
|
||||
var normalized = this._normalizeArabic(searchLower);
|
||||
var visible = 0;
|
||||
var limit = this.state.limit || 80;
|
||||
var matchedIds = [];
|
||||
|
||||
this.$('.o_data_row').each(function() {
|
||||
var $row = $(this);
|
||||
var text = $row.text().toLowerCase();
|
||||
var normalizedText = this._normalizeArabic ? this._normalizeArabic(text) : 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'));
|
||||
if (rowId) matchedIds.push(rowId);
|
||||
} else {
|
||||
$row.hide();
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
// Update state
|
||||
this._search.isFiltered = true;
|
||||
this._search.filteredIds = matchedIds;
|
||||
|
||||
// Update count
|
||||
var msg = visible > 0 ?
|
||||
_t('Found: ') + visible + _t(' records') :
|
||||
_t('No records found');
|
||||
this.$('.oe_search_count').text(msg).show();
|
||||
|
||||
if (visible === 0) {
|
||||
this._showNoResults();
|
||||
}
|
||||
|
||||
this._updatePager(visible);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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
|
||||
this._search.value = '';
|
||||
this._search.filteredIds = null;
|
||||
this._search.allFilteredRecords = null;
|
||||
this._search.isFiltered = false;
|
||||
|
||||
// Restore original data
|
||||
if (this._search.originalData) {
|
||||
// Restore state
|
||||
if (this.state) {
|
||||
if (this.state.data) {
|
||||
this.state.data.records = this._search.originalData.records;
|
||||
}
|
||||
this.state.count = this._search.originalData.count;
|
||||
this.state.res_ids = this._search.originalData.res_ids;
|
||||
this.state.domain = this._search.originalDomain || [];
|
||||
}
|
||||
|
||||
// Show all rows with limit
|
||||
var $rows = this.$('.o_data_row');
|
||||
var limit = this._search.originalData.limit || 80;
|
||||
|
||||
$rows.each(function(index) {
|
||||
$(this).toggle(index < limit);
|
||||
});
|
||||
|
||||
// Restore pager
|
||||
this._updatePager(this._search.originalData.count);
|
||||
|
||||
// Clear stored data
|
||||
this._search.originalData = null;
|
||||
this._search.originalDomain = null;
|
||||
} else {
|
||||
// Just show rows with limit
|
||||
var $rows = this.$('.o_data_row');
|
||||
var limit = this.state.limit || 80;
|
||||
|
||||
$rows.each(function(index) {
|
||||
$(this).toggle(index < limit);
|
||||
});
|
||||
|
||||
if (this.state) {
|
||||
this._updatePager(this.state.count || $rows.length);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Store original data
|
||||
*/
|
||||
_storeOriginalData: function() {
|
||||
if (this.state) {
|
||||
this._search.originalData = {
|
||||
records: this.state.data && this.state.data.records ?
|
||||
this.state.data.records.slice() : [],
|
||||
count: this.state.count || 0,
|
||||
res_ids: this.state.res_ids ? this.state.res_ids.slice() : [],
|
||||
limit: this.state.limit || 80,
|
||||
offset: this.state.offset || 0
|
||||
};
|
||||
this._search.originalDomain = this.state.domain ? this.state.domain.slice() : [];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Build search domain
|
||||
*/
|
||||
|
|
@ -312,359 +674,7 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
|
|||
},
|
||||
|
||||
/**
|
||||
* Display search results
|
||||
*/
|
||||
_displayResults: function(records) {
|
||||
var self = this;
|
||||
var count = records.length;
|
||||
|
||||
// Show count
|
||||
this.$('.oe_search_count').text(_t('Found: ') + count + _t(' records')).show();
|
||||
|
||||
if (count === 0) {
|
||||
this._showNoResults();
|
||||
return;
|
||||
}
|
||||
|
||||
// Store filtered IDs
|
||||
this._search.filteredIds = records.map(function(r) { return r.id; });
|
||||
|
||||
// Apply filter to current view
|
||||
this._applySearchFilter(this._search.filteredIds);
|
||||
},
|
||||
|
||||
/**
|
||||
* Apply search filter to current rows
|
||||
*/
|
||||
_applySearchFilter: function(filteredIds) {
|
||||
var self = this;
|
||||
|
||||
// Method 1: Try DOM filtering first (fastest, preserves input)
|
||||
var $rows = this.$('.o_data_row');
|
||||
if ($rows.length > 0) {
|
||||
var visibleCount = 0;
|
||||
var limit = this.state.limit || 80;
|
||||
|
||||
$rows.each(function() {
|
||||
var $row = $(this);
|
||||
var rowId = self._getRowId($row);
|
||||
|
||||
if (rowId && filteredIds.includes(rowId) && visibleCount < limit) {
|
||||
$row.show();
|
||||
visibleCount++;
|
||||
} else {
|
||||
$row.hide();
|
||||
}
|
||||
});
|
||||
|
||||
// If we have visible rows, we're done
|
||||
if (visibleCount > 0) {
|
||||
this._updatePager(filteredIds.length);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Method 2: If no rows visible or no rows in DOM, need to render new data
|
||||
if (this._search.filteredRecords) {
|
||||
this._renderFilteredRecords(this._search.filteredRecords);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Render filtered records when DOM filtering is not enough
|
||||
*/
|
||||
_renderFilteredRecords: function(records) {
|
||||
var self = this;
|
||||
|
||||
// Get records for current page
|
||||
var limit = this.state.limit || 80;
|
||||
var pageRecords = records.slice(0, limit);
|
||||
|
||||
// Update state data
|
||||
if (this.state && this.state.data) {
|
||||
// Store original records reference
|
||||
if (!this._search.originalRecords) {
|
||||
this._search.originalRecords = this.state.data.records;
|
||||
}
|
||||
|
||||
// Update with filtered records
|
||||
this.state.data.records = pageRecords;
|
||||
this.state.count = records.length;
|
||||
this.state.res_ids = records.map(function(r) { return r.id; });
|
||||
}
|
||||
|
||||
// Remove old rows
|
||||
this.$('tbody .o_data_row').remove();
|
||||
this.$('.oe_no_results').remove();
|
||||
|
||||
// Try to use Odoo's internal _renderRows (without parameters)
|
||||
if (typeof this._renderRows === 'function') {
|
||||
try {
|
||||
// _renderRows uses state.data.records internally
|
||||
var $rows = this._renderRows();
|
||||
if ($rows && $rows.length) {
|
||||
this.$('tbody').append($rows);
|
||||
} else {
|
||||
// Fallback if no rows returned
|
||||
this._createBasicRows(pageRecords);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('_renderRows failed:', err);
|
||||
this._createBasicRows(pageRecords);
|
||||
}
|
||||
} else if (typeof this._renderBody === 'function') {
|
||||
try {
|
||||
// Try _renderBody as alternative
|
||||
var result = this._renderBody();
|
||||
if (result && typeof result.then === 'function') {
|
||||
result.then(function() {
|
||||
// Body rendered
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('_renderBody failed:', err);
|
||||
this._createBasicRows(pageRecords);
|
||||
}
|
||||
} else {
|
||||
// Direct fallback
|
||||
this._createBasicRows(pageRecords);
|
||||
}
|
||||
|
||||
// Update pager
|
||||
this._updatePager(records.length);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create basic rows as fallback
|
||||
*/
|
||||
_createBasicRows: function(records) {
|
||||
var self = this;
|
||||
|
||||
records.forEach(function(record) {
|
||||
var $row = $('<tr class="o_data_row"></tr>');
|
||||
$row.data('id', record.id);
|
||||
|
||||
// Add cells for each column
|
||||
if (self.columns) {
|
||||
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) {
|
||||
value = value[1] || value.display_name || '';
|
||||
}
|
||||
var $cell = $('<td class="o_data_cell"></td>');
|
||||
$cell.text(value);
|
||||
$cell.attr('data-field', col.attrs.name);
|
||||
$row.append($cell);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
self.$('tbody').append($row);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Update pager with new count
|
||||
*/
|
||||
_updatePager: function(totalCount) {
|
||||
if (this.getParent() && this.getParent().pager) {
|
||||
this.getParent().pager.updateState({
|
||||
current_min: totalCount > 0 ? 1 : 0,
|
||||
size: totalCount,
|
||||
limit: this.state.limit || 80
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get row ID from DOM element
|
||||
*/
|
||||
_getRowId: function($row) {
|
||||
// Method 1: data-id attribute
|
||||
var id = $row.data('id');
|
||||
if (id) return parseInt(id);
|
||||
|
||||
// Method 2: 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;
|
||||
}
|
||||
|
||||
// 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 for o_res_id class
|
||||
var classes = $row.attr('class') || '';
|
||||
var match = classes.match(/o_res_id_(\d+)/);
|
||||
if (match) return parseInt(match[1]);
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Client-side search fallback
|
||||
*/
|
||||
_clientSideSearch: function(value) {
|
||||
var searchLower = value.toLowerCase();
|
||||
var normalized = this._normalizeArabic(searchLower);
|
||||
var visible = 0;
|
||||
var limit = this.state.limit || 80;
|
||||
var matchedIds = [];
|
||||
|
||||
this.$('.o_data_row').each(function() {
|
||||
var $row = $(this);
|
||||
var text = $row.text().toLowerCase();
|
||||
var match = text.includes(searchLower) || text.includes(normalized);
|
||||
|
||||
if (match && visible < limit) {
|
||||
$row.show();
|
||||
visible++;
|
||||
var rowId = parseInt($row.data('id'));
|
||||
if (rowId) matchedIds.push(rowId);
|
||||
} else {
|
||||
$row.hide();
|
||||
}
|
||||
});
|
||||
|
||||
// Update search state
|
||||
this._search.isFiltered = true;
|
||||
this._search.filteredIds = matchedIds;
|
||||
|
||||
// Update count
|
||||
var msg = visible > 0 ?
|
||||
_t('Found: ') + visible + _t(' records') :
|
||||
_t('No records found');
|
||||
this.$('.oe_search_count').text(msg).show();
|
||||
|
||||
if (visible === 0) {
|
||||
this._showNoResults();
|
||||
}
|
||||
|
||||
// Update pager
|
||||
this._updatePager(visible);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear search and restore original view
|
||||
*/
|
||||
_clearSearch: function() {
|
||||
// Clear UI
|
||||
this.$('.oe_search_input').val('');
|
||||
this.$('.oe_clear_search').hide();
|
||||
this.$('.oe_search_count').hide();
|
||||
this.$('.oe_no_results').remove();
|
||||
|
||||
// Clear state
|
||||
this._search.value = '';
|
||||
this._search.filteredIds = null;
|
||||
this._search.filteredRecords = null;
|
||||
this._search.isFiltered = false;
|
||||
|
||||
// Show all rows or restore original
|
||||
if (this._search.originalData) {
|
||||
this._restoreOriginalData();
|
||||
} else {
|
||||
// Show all rows with pagination
|
||||
var $rows = this.$('.o_data_row');
|
||||
var limit = this.state.limit || 80;
|
||||
|
||||
$rows.each(function(index) {
|
||||
$(this).toggle(index < limit);
|
||||
});
|
||||
|
||||
// Restore pager
|
||||
if (this.state) {
|
||||
this._updatePager(this.state.count || $rows.length);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Store original data
|
||||
*/
|
||||
_storeOriginalData: function() {
|
||||
if (this.state) {
|
||||
this._search.originalData = {
|
||||
records: this.state.data && this.state.data.records ?
|
||||
this.state.data.records.slice() : [],
|
||||
count: this.state.count || 0,
|
||||
res_ids: this.state.res_ids ? this.state.res_ids.slice() : [],
|
||||
limit: this.state.limit,
|
||||
offset: this.state.offset || 0
|
||||
};
|
||||
this._search.originalDomain = this.state.domain ? this.state.domain.slice() : [];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Restore original data
|
||||
*/
|
||||
_restoreOriginalData: function() {
|
||||
if (!this._search.originalData) return;
|
||||
|
||||
var orig = this._search.originalData;
|
||||
|
||||
// Restore state
|
||||
if (this.state) {
|
||||
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 || [];
|
||||
}
|
||||
|
||||
// Show all original rows
|
||||
var $rows = this.$('.o_data_row');
|
||||
$rows.show();
|
||||
|
||||
// Hide rows beyond limit
|
||||
var limit = orig.limit || 80;
|
||||
$rows.each(function(index) {
|
||||
if (index >= limit) {
|
||||
$(this).hide();
|
||||
}
|
||||
});
|
||||
|
||||
// Restore pager
|
||||
this._updatePager(orig.count);
|
||||
|
||||
// Clear stored data
|
||||
this._search.originalData = null;
|
||||
this._search.originalDomain = null;
|
||||
this._search.originalRecords = 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
|
||||
* Show loading
|
||||
*/
|
||||
_showLoading: function(show) {
|
||||
this.$('.oe_search_loading').toggle(show);
|
||||
|
|
|
|||
Loading…
Reference in New Issue