From 8aba15b06428c2f5c46c5d010844fdf75aa27e4b Mon Sep 17 00:00:00 2001 From: Mohamed Eltayar <152964073+maltayyar2@users.noreply.github.com> Date: Sat, 30 Aug 2025 15:41:23 +0300 Subject: [PATCH] Optimize and clean code: Remove RTL/language specific logic, simplify search implementation --- .../static/src/js/list_search.js | 406 ++++-------------- 1 file changed, 72 insertions(+), 334 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 42db5fc16..d77caf742 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 @@ -9,17 +9,9 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { var rpc = require('web.rpc'); var session = require('web.session'); - // ================================ - // ListController: Data operations - // ================================ ListController.include({ - - /** - * @override - */ init: function () { this._super.apply(this, arguments); - // Initialize search state this._customSearchState = { timer: null, value: '', @@ -28,54 +20,34 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { originalDomain: null, searchInProgress: false, lastSearchPromise: null, - lastSearchValue: '' // Track last search value + lastSearchValue: '' }; this._searchMutex = new concurrency.Mutex(); }, - /** - * @override - Hook into rendering complete - */ renderButtons: function () { this._super.apply(this, arguments); - // Setup search handler after buttons are rendered this._setupCustomSearchHandler(); }, - /** - * Setup custom search handler - */ _setupCustomSearchHandler: function() { - var self = this; - // Ensure renderer is ready if (this.renderer && this.renderer._customSearchReady) { - console.log('Custom search handler already setup'); return; } }, - /** - * Handle custom search from controller - */ _handleCustomSearch: function(value) { var self = this; - - // Check if value actually changed if (value === self._customSearchState.lastSearchValue) { - console.log('Search value unchanged, skipping'); return Promise.resolve(); } - - // Update last search value self._customSearchState.lastSearchValue = value; - // Cancel any pending search if (self._customSearchState.timer) { clearTimeout(self._customSearchState.timer); self._customSearchState.timer = null; } - // Debounce search return new Promise(function(resolve) { self._customSearchState.timer = setTimeout(function() { self._executeCustomSearch(value).then(resolve); @@ -83,36 +55,26 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { }); }, - /** - * Execute the search - */ _executeCustomSearch: function(value) { var self = this; if (self._customSearchState.searchInProgress) { - console.log('Search already in progress, queueing'); return self._customSearchState.lastSearchPromise.then(function() { return self._executeCustomSearch(value); }); } - console.log('=== EXECUTING CUSTOM SEARCH ==='); - console.log('Search value:', value); - var searchPromise = this._searchMutex.exec(function() { self._customSearchState.searchInProgress = true; if (!value || value.length === 0) { - console.log('Empty search - clearing filters'); return self._clearCustomSearch(); } - // Store original domain on first search if (!self._customSearchState.originalDomain && !self._customSearchState.isFiltered) { var currentState = self.model.get(self.handle); if (currentState && currentState.domain) { self._customSearchState.originalDomain = JSON.parse(JSON.stringify(currentState.domain)); - console.log('Stored original domain:', self._customSearchState.originalDomain); } } @@ -126,87 +88,55 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { return searchPromise; }, - /** - * Apply search using proper Odoo 14 method - */ _applyCustomSearch: function(value) { var self = this; - - // Build search domain var searchDomain = this._buildCustomSearchDomain(value); + if (!searchDomain || searchDomain.length === 0) { - console.warn('No searchable fields found or no valid search domain'); return Promise.resolve(); } - // Combine with original domain var finalDomain = this._combineCustomDomains(searchDomain); - console.log('Search domain:', searchDomain); - console.log('Final domain:', finalDomain); - - // Show loading state if (self.renderer) { self.renderer.$('.oe_search_loading').show(); self.renderer.$('.oe_search_count').hide(); } - // First get the count return rpc.query({ model: this.modelName, method: 'search_count', args: [finalDomain], context: session.user_context }).then(function(count) { - console.log('Found records:', count); - - // Update UI with count self._customSearchState.filteredCount = count; self._updateCustomSearchUI(count); - // Now reload the view with new domain - // CRITICAL FIX: Use proper reload method for Odoo 14 var handle = self.handle; var state = self.model.get(handle); - // Update the domain in the state return self.model.reload(handle, { domain: finalDomain, - offset: 0, // Reset to first page + offset: 0, limit: state.limit || 80 }); }).then(function() { - // Update the view self._customSearchState.isFiltered = true; self._customSearchState.value = value; - - // Trigger update to renderer WITHOUT reload - // This maintains the search box value return self.update({}, {reload: false}); - }).then(function() { - console.log('Search applied successfully'); - return Promise.resolve(); }).catch(function(error) { - console.error('Search error:', error); self._showSearchError(); return Promise.resolve(); }).finally(function() { - // Hide loading if (self.renderer) { self.renderer.$('.oe_search_loading').hide(); } }); }, - /** - * Clear custom search - */ _clearCustomSearch: function() { var self = this; - console.log('=== CLEARING CUSTOM SEARCH ==='); - - // Clear UI immediately if (this.renderer && this.renderer.$) { this.renderer.$('.oe_search_input').val(''); this.renderer.$('.oe_clear_search').hide(); @@ -214,19 +144,14 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { this.renderer.$('.oe_search_loading').show(); } - // Reset state this._customSearchState.value = ''; this._customSearchState.isFiltered = false; this._customSearchState.filteredCount = 0; this._customSearchState.lastSearchValue = ''; - // Get original domain var originalDomain = this._customSearchState.originalDomain || []; this._customSearchState.originalDomain = null; - console.log('Restoring original domain:', originalDomain); - - // Reload with original domain var handle = this.handle; var state = this.model.get(handle); @@ -243,101 +168,63 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { }); }, - /** - * Build search domain - FIXED VERSION - */ _buildCustomSearchDomain: function(value) { if (!value) return []; var fields = this._getCustomSearchableFields(); - if (!fields || fields.length === 0) { - console.warn('No searchable fields found'); - return []; - } + if (!fields || fields.length === 0) return []; var conditions = []; - var normalized = this._normalizeArabic(value); + var normalized = this._normalizeText(value); var searchValues = [value]; if (normalized !== value) { searchValues.push(normalized); } - console.log('Building domain for fields:', fields); - fields.forEach(function(field) { - try { - var fieldType = field.type; - var fieldName = field.name; - - switch(fieldType) { - case 'char': - case 'text': - case 'html': - searchValues.forEach(function(searchVal) { - conditions.push([fieldName, 'ilike', searchVal]); - }); - break; - - case 'integer': - case 'float': - case 'monetary': - var numValue = parseFloat(value); - if (!isNaN(numValue)) { - // For numeric fields, also search as string - conditions.push([fieldName, '=', numValue]); - // Also search string representation - conditions.push([fieldName, 'ilike', value]); - } - break; - - case 'many2one': - // CRITICAL FIX: For many2one, search on display_name - searchValues.forEach(function(searchVal) { - // Direct search on the field (searches display_name by default) - conditions.push([fieldName, 'ilike', searchVal]); - }); - break; - - case 'selection': - searchValues.forEach(function(searchVal) { - conditions.push([fieldName, 'ilike', searchVal]); - }); - break; - - case 'boolean': - var lowerValue = value.toLowerCase().trim(); - var booleanMappings = { - 'true': true, 'yes': true, 'نعم': true, '1': true, 'صح': true, - 'false': false, 'no': false, 'لا': false, '0': false, 'خطأ': false - }; - if (lowerValue in booleanMappings) { - conditions.push([fieldName, '=', booleanMappings[lowerValue]]); - } - break; - - case 'date': - case 'datetime': - // Try to parse as date - if (value.match(/^\d{4}-\d{2}-\d{2}/)) { - conditions.push([fieldName, 'ilike', value]); - } - break; - } - } catch (error) { - console.warn('Error processing field', field.name, ':', error); + var fieldType = field.type; + var fieldName = field.name; + + switch(fieldType) { + case 'char': + case 'text': + case 'html': + case 'many2one': + case 'selection': + searchValues.forEach(function(searchVal) { + conditions.push([fieldName, 'ilike', searchVal]); + }); + break; + case 'integer': + case 'float': + case 'monetary': + var numValue = parseFloat(value); + if (!isNaN(numValue)) { + conditions.push([fieldName, '=', numValue]); + } + break; + case 'boolean': + var lowerValue = value.toLowerCase().trim(); + var boolMap = { + 'true': true, 'yes': true, '1': true, + 'false': false, 'no': false, '0': false + }; + if (lowerValue in boolMap) { + conditions.push([fieldName, '=', boolMap[lowerValue]]); + } + break; + case 'date': + case 'datetime': + if (value.match(/^\d{4}-\d{2}-\d{2}/)) { + conditions.push([fieldName, 'ilike', value]); + } + break; } }); - if (conditions.length === 0) { - return []; - } + if (conditions.length === 0) return []; + if (conditions.length === 1) return conditions[0]; - // Build proper OR domain - if (conditions.length === 1) { - return conditions[0]; - } - - // Create OR domain: ['|', ['field1', 'op', 'val'], ['field2', 'op', 'val']] var orDomain = []; for (var i = 1; i < conditions.length; i++) { orDomain.push('|'); @@ -345,23 +232,16 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { return orDomain.concat(conditions); }, - /** - * Get searchable fields - ENHANCED VERSION - */ _getCustomSearchableFields: function() { var fields = []; var state = this.model.get(this.handle); - if (!state) { - console.warn('No state available'); - return fields; - } + if (!state) return fields; var fieldDefs = state.fields || {}; var fieldsInfo = state.fieldsInfo; var viewType = state.viewType || 'list'; - // Method 1: Get from fieldsInfo (most reliable) if (fieldsInfo && fieldsInfo[viewType]) { Object.keys(fieldsInfo[viewType]).forEach(function(fieldName) { var fieldInfo = fieldsInfo[viewType][fieldName]; @@ -378,7 +258,6 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { }); } - // Method 2: Get from renderer columns if available if (fields.length === 0 && this.renderer && this.renderer.columns) { this.renderer.columns.forEach(function(col) { if (!col.invisible && col.attrs && col.attrs.name) { @@ -395,10 +274,8 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { }); } - // Method 3: Fallback to common searchable fields if (fields.length === 0) { - var commonFields = ['name', 'display_name', 'code', 'reference', 'ref', 'description']; - commonFields.forEach(function(fname) { + ['name', 'display_name', 'code', 'reference'].forEach(function(fname) { if (fieldDefs[fname] && fieldDefs[fname].store !== false) { fields.push({ name: fname, @@ -409,106 +286,57 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { }); } - console.log('Searchable fields found:', fields); return fields; }, - /** - * Combine domains - FIXED VERSION - */ _combineCustomDomains: function(searchDomain) { var originalDomain = this._customSearchState.originalDomain || []; - // Handle empty domains - if (!searchDomain || searchDomain.length === 0) { - return originalDomain; - } - if (!originalDomain || originalDomain.length === 0) { - return searchDomain; - } + if (!searchDomain || searchDomain.length === 0) return originalDomain; + if (!originalDomain || originalDomain.length === 0) return searchDomain; - // Combine with AND operator - // Ensure we're working with arrays var origArray = Array.isArray(originalDomain) ? originalDomain : [originalDomain]; var searchArray = Array.isArray(searchDomain) ? searchDomain : [searchDomain]; - // Create combined domain: ['&', original_domain, search_domain] return ['&'].concat(origArray).concat(searchArray); }, - /** - * Update search UI - */ _updateCustomSearchUI: function(count) { if (this.renderer && this.renderer.$) { - var isArabic = this._isArabicLanguage(); - var message = isArabic ? - 'عدد السجلات: ' + count : - _t('Found: ') + count + _t(' records'); - this.renderer.$('.oe_search_count') - .text(message) + .text(_t('Found: ') + count + _t(' records')) + .removeClass('text-danger') .show(); this.renderer.$('.oe_clear_search').show(); } }, - /** - * Show search error - */ _showSearchError: function() { if (this.renderer && this.renderer.$) { - var isArabic = this._isArabicLanguage(); - var errorMsg = isArabic ? 'حدث خطأ في البحث' : _t('Search error occurred'); - this.renderer.$('.oe_search_count') - .text(errorMsg) + .text(_t('Search error occurred')) .addClass('text-danger') .show(); } }, - /** - * Check if system language is Arabic - */ - _isArabicLanguage: function() { - var lang = session.user_context.lang || ''; - return lang.startsWith('ar'); - }, - - /** - * Normalize Arabic text - ENHANCED VERSION - */ - _normalizeArabic: function(text) { + _normalizeText: function(text) { if (!text) return ''; - return text - // Remove Arabic diacritics .replace(/[\u064B-\u065F]/g, '') - // Convert Arabic-Indic digits to Western digits .replace(/[٠-٩]/g, function(d) { return String.fromCharCode(d.charCodeAt(0) - 0x0660 + 0x0030); }) - // Convert Persian digits to Western digits .replace(/[۰-۹]/g, function(d) { return String.fromCharCode(d.charCodeAt(0) - 0x06F0 + 0x0030); }) - // Normalize Alef variations .replace(/[آأإا]/g, 'ا') - // Normalize Teh Marbuta .replace(/ة/g, 'ه') - // Normalize Yeh variations .replace(/[يئى]/g, 'ي') - // Normalize Waw variations - .replace(/[ؤو]/g, 'و') - // Trim spaces .trim(); } }); - // ================================ - // ListRenderer: UI only - // ================================ ListRenderer.include({ events: _.extend({}, ListRenderer.prototype.events, { 'keyup .oe_search_input': '_onCustomSearchKeyUp', @@ -517,47 +345,33 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { 'keydown .oe_search_input': '_onCustomSearchKeyDown' }), - /** - * @override - */ init: function() { this._super.apply(this, arguments); this._searchTimer = null; this._customSearchReady = false; - this._lastInputValue = ''; // Track last input value + this._lastInputValue = ''; }, - /** - * @override - */ _renderView: function () { var self = this; return this._super.apply(this, arguments).then(function (result) { - // Add search box for tree views if (self._shouldAddSearchBox()) { self._addCustomSearchBox(); self._customSearchReady = true; - - // Restore search state after render self._restoreSearchState(); } return result; }); }, - /** - * Restore search state after render - */ _restoreSearchState: function() { var controller = this.getParent(); if (controller && controller._customSearchState) { var state = controller._customSearchState; - // Restore search input value if (state.value) { var $input = this.$('.oe_search_input'); $input.val(state.value); - // Set cursor position to end of text var length = state.value.length; if ($input[0] && $input[0].setSelectionRange) { $input[0].setSelectionRange(length, length); @@ -566,32 +380,15 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { this._lastInputValue = state.value; } - // Restore count display if (state.isFiltered && state.filteredCount >= 0) { - var isArabic = this._isArabicLanguage(); - var message = isArabic ? - 'عدد السجلات: ' + state.filteredCount : - _t('Found: ') + state.filteredCount + _t(' records'); - this.$('.oe_search_count') - .text(message) + .text(_t('Found: ') + state.filteredCount + _t(' records')) .removeClass('text-danger') .show(); } } }, - /** - * Check if system language is Arabic - */ - _isArabicLanguage: function() { - var lang = session.user_context.lang || ''; - return lang.startsWith('ar'); - }, - - /** - * Check if we should add search box - */ _shouldAddSearchBox: function() { return this.arch && this.arch.tag === 'tree' && @@ -600,11 +397,7 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { !this.$el.find('.oe_search_container').length; }, - /** - * Add search box UI - */ _addCustomSearchBox: function() { - // Check if already exists or if we have a saved state var controller = this.getParent(); var savedValue = ''; var savedCount = 0; @@ -616,44 +409,31 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { isFiltered = controller._customSearchState.isFiltered || false; } - var isArabic = this._isArabicLanguage(); - var dir = isArabic ? 'rtl' : 'ltr'; - - // Translations - var placeholder = isArabic ? 'البحث في جميع الأعمدة المرئية...' : _t('Search in all visible columns...'); - var clearText = isArabic ? 'مسح' : _t('Clear'); - var countText = ''; - if (isFiltered) { - countText = isArabic ? - 'عدد السجلات: ' + savedCount : - _t('Found: ') + savedCount + _t(' records'); - } + var countText = isFiltered ? + _t('Found: ') + savedCount + _t(' records') : ''; var html = - '