diff --git a/odex25_base/fims_general_search_tree_view/__manifest__.py b/odex25_base/fims_general_search_tree_view/__manifest__.py index 535bee9e8..306e54985 100644 --- a/odex25_base/fims_general_search_tree_view/__manifest__.py +++ b/odex25_base/fims_general_search_tree_view/__manifest__.py @@ -1,95 +1,13 @@ # -*- coding: utf-8 -*- -############################################################################### -# -# Fortutech IMS Pvt. Ltd. -# Copyright (C) 2016-TODAY Fortutech IMS Pvt. Ltd.(). -# -############################################################################### { - 'name': 'General Search in Tree/List View - All Records [ENHANCED]', + 'name': 'Advanced Tree View Search', 'category': 'Tools', - 'summary': 'Enhanced search across all records in tree/list views with accurate count and optimized performance', - 'version': '14.0.4.0', + 'summary': 'Instant search across all visible columns in list views', + 'version': '14.0.5.0', 'license': 'OPL-1', 'description': """ - Enhanced General Search in Tree/List View - FULLY OPTIMIZED & FIXED - ==================================================================== - - This module provides powerful, accurate search functionality for all tree/list views - using the most advanced Odoo integration methodology: - - ✅ CORE FUNCTIONALITY: - * Searches across ALL records in the database (not just visible page records) - * Searches in ALL visible columns of the current list view automatically - * Uses proper Odoo trigger_up('do_search') methodology - ZERO custom DOM manipulation - * Maintains 100% compatibility with all Odoo functionality - * Records are fully clickable and work exactly like standard Odoo - * Perfect integration with existing search domains, filters, and grouping - - 🎯 ENHANCED FEATURES (Version 4.0): - * Accurate record count using RPC search_count method - * Real-time search with 500ms intelligent debounce - * Advanced Arabic text normalization and search support - * Multi-field type support: char, text, integer, float, many2one, selection, boolean - * Enhanced loading states with clear user feedback - * Comprehensive error handling and graceful fallbacks - * Smart field detection from visible columns - * Advanced domain combination logic - * Memory leak prevention and proper cleanup - - 🚀 TECHNICAL EXCELLENCE: - * Fixed critical duplicate method definitions - * Enhanced mutex-based concurrency control - * Proper domain deep copying to avoid reference issues - * Optimized search state management and restoration - * Comprehensive logging for debugging and monitoring - * Zero interference with Odoo's native rendering - * Clean separation between UI and business logic - - 🌍 MULTILINGUAL SUPPORT: - * Full Arabic text support with advanced normalization - * Handles Arabic diacritics, digit variations, and character normalization - * Support for various text encodings and formats - * Intelligent boolean value matching (yes/no, نعم/لا, true/false) - - 💡 USER EXPERIENCE: - * Intuitive search interface with clear visual feedback - * Shows accurate count of found records across ALL pages - * Clear button for easy search reset - * Loading indicators during search operations - * Non-intrusive design that complements existing UI - * Maintains all standard Odoo keyboard shortcuts and interactions - - 🔧 COMPATIBILITY & INTEGRATION: - * Works with all Odoo 14 list/tree views automatically - * Compatible with existing search domains and filters - * Respects view permissions and access rights - * Works with grouped views and complex domains - * Integrates seamlessly with existing customizations - * Zero configuration required - works out of the box - - Version 4.0 - COMPLETE OPTIMIZATION & BUG FIXES: - ------------------------------------------------ - ✅ FIXED: Duplicate _renderView method definitions causing conflicts - ✅ FIXED: Inaccurate record counting (now uses proper RPC search_count) - ✅ FIXED: Search state restoration issues and infinite loops - ✅ ENHANCED: Domain combination logic with deep copying - ✅ ENHANCED: Field type detection and search logic - ✅ ENHANCED: Error handling and graceful degradation - ✅ ENHANCED: Performance optimizations and memory management - ✅ ENHANCED: Arabic text normalization algorithms - ✅ ENHANCED: User interface feedback and loading states - - Implementation Highlights: - ------------------------- - - Uses official Odoo ListRenderer.include() pattern - - Implements proper trigger_up('do_search') for filtering - - Leverages RPC search_count for accurate record counting - - Builds standard Odoo domain syntax for maximum compatibility - - Maintains original view domains while adding search conditions - - Zero custom record rendering - uses Odoo's native methods - - Comprehensive field type support with intelligent matching - - Advanced Arabic text processing and normalization + Advanced search functionality for Odoo list views. + Provides instant search across all visible columns with real-time filtering and accurate record counting. """, 'depends': ['base', 'web'], 'author': "Fortutech IMS Pvt. Ltd.", @@ -97,8 +15,8 @@ 'data': [ 'views/assets.xml', ], - "installable": True, - "application": False, - "auto_install": False, - "images": ['static/description/banner.png'], + 'installable': True, + 'application': False, + 'auto_install': False, + 'images': ['static/description/banner.png'], } \ No newline at end of file diff --git a/odex25_base/fims_general_search_tree_view/static/description/index.html b/odex25_base/fims_general_search_tree_view/static/description/index.html index 5d0e70d05..e52dca73b 100644 --- a/odex25_base/fims_general_search_tree_view/static/description/index.html +++ b/odex25_base/fims_general_search_tree_view/static/description/index.html @@ -1,123 +1,41 @@ -
-
-
-
-
-
    -
    Key Features
    -
  • This Module is used to each list or tree views.
  • -
  • Immediately responce when you press any Key in quick search field.
  • -
-
-
-
+ + + + + Advanced Tree View Search + + +
+
+ Advanced Tree View Search +
+ +

Advanced Tree View Search

+ +

+ Instant search across all visible columns in Odoo list views with real-time filtering. +

+ +

Features

+
    +
  • Search all records in database (not just visible page)
  • +
  • Real-time search with intelligent debounce
  • +
  • Accurate record counting
  • +
  • Works with all field types
  • +
  • Automatic RTL/LTR support
  • +
  • No configuration required
  • +
+ +

Screenshots

+
+ Search Box + Search Results +
+ +
+ Fortutech IMS +

© Fortutech IMS Pvt. Ltd.

-
-
-
-
-
-
- - Search -
-

- -

-

- -

-
- - Result -
-

- - -

-
-
-
-
-
-
-
-
-
-
-
    -
    Our Service
    -
- - - - - - - - - - - -
Odoo CustomizationOdoo ImplementationOdoo Integration
-

- - - - - - - - - - - -
Odoo MigrationOdoo TrainingOdoo Installation
-

- - - - - - - - - - - -
Odoo ConsultingOdoo SupportHire Developer
-
-
-
-
-
-
-
-
-
-
-
    -
    Get Help & Support
    -
  • You will get 30 Days free support incase any bugs or issue.
  • -
    -
  • -

    -
- - - - - - - - - - - -
www.fortutechims.cominfo@fortutechims.cominfo@fortutechims.com
-
-
-
-
-
\ No newline at end of file + + \ No newline at end of file diff --git a/odex25_base/fims_general_search_tree_view/static/src/css/list_search.css b/odex25_base/fims_general_search_tree_view/static/src/css/list_search.css index de65349d0..49b6a711a 100644 --- a/odex25_base/fims_general_search_tree_view/static/src/css/list_search.css +++ b/odex25_base/fims_general_search_tree_view/static/src/css/list_search.css @@ -11,45 +11,41 @@ padding: 12px; } -/* Enhanced search container styles */ .oe_search_container { background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); padding: 12px; border-radius: 6px; - margin-bottom: 12px; + margin: 8px; border: 1px solid #dee2e6; box-shadow: 0 1px 3px rgba(0,0,0,0.1); transition: all 0.3s ease; + gap: 8px; } .oe_search_container:hover { box-shadow: 0 2px 6px rgba(0,0,0,0.15); } -/* Enhanced search input styles */ .oe_search_input { - transition: all 0.3s ease; + border: 1px solid #ced4da !important; + height: 36px; + padding: 0 12px; border-radius: 4px; font-size: 14px; + transition: all 0.3s ease; } .oe_search_input:focus { border-color: #007bff !important; box-shadow: 0 0 0 0.2rem rgba(0,123,255,.25) !important; - outline: none; background-color: #fff; } -.oe_search_input::placeholder { - color: #6c757d; - font-style: italic; -} - -/* Enhanced clear button */ .oe_clear_search { min-width: 70px; border-radius: 4px; transition: all 0.2s ease; + white-space: nowrap; } .oe_clear_search:hover { @@ -58,80 +54,24 @@ transform: translateY(-1px); } -/* Enhanced search count display */ .oe_search_count { - color: #28a745; font-size: 0.9em; font-weight: 600; - padding: 4px 8px; - background-color: rgba(40, 167, 69, 0.1); - border-radius: 4px; - border: 1px solid rgba(40, 167, 69, 0.2); - transition: all 0.3s ease; + padding: 6px 10px; + white-space: nowrap; } -/* Enhanced loading indicator */ .oe_search_loading { - display: inline-block; - margin-left: 10px; color: #007bff; } -.oe_search_loading i { - animation: enhanced-search-spin 1s linear infinite; -} - -/* Responsive design improvements */ @media (max-width: 768px) { .oe_search_container { - flex-direction: column; - gap: 10px; + flex-wrap: wrap; } .oe_search_input { width: 100%; - margin-bottom: 10px; + margin-bottom: 8px; } - - .oe_clear_search, - .oe_search_count { - align-self: flex-start; - } -} - -/* Enhanced loading animation */ -@keyframes enhanced-search-spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -/* Accessibility improvements */ -.oe_search_input:focus-visible { - outline: 2px solid #007bff; - outline-offset: 2px; -} - -.oe_clear_search:focus-visible { - outline: 2px solid #007bff; - outline-offset: 2px; -} - -/* Arabic text support enhancements */ -.oe_search_input[dir="rtl"] { - text-align: right; - direction: rtl; -} - -.oe_search_container[dir="rtl"] { - direction: rtl; -} - -.oe_search_container[dir="rtl"] .oe_clear_search { - margin-right: 8px; - margin-left: 0; -} - -.oe_search_container[dir="rtl"] .oe_search_count { - margin-right: 8px; - margin-left: 0; } \ No newline at end of file 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 = - '
' + - '' + + '' + - '' + - '' + + '' + countText + '' + - '
'; this.$el.prepend($(html)); - - // Store initial value this._lastInputValue = savedValue; - // Focus on search input if it has value and set cursor to end if (savedValue) { var $input = this.$('.oe_search_input'); $input.focus(); @@ -666,17 +446,11 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { } }, - /** - * Handle input events - */ _onCustomSearchInput: function(e) { var currentValue = $(e.currentTarget).val(); var hasValue = !!currentValue.trim(); - // Only handle if value actually changed (not just cursor movement) - if (currentValue === this._lastInputValue) { - return; - } + if (currentValue === this._lastInputValue) return; this._lastInputValue = currentValue; this.$('.oe_clear_search').toggle(hasValue); @@ -686,59 +460,30 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { } }, - /** - * Handle keyup events with debounce - */ _onCustomSearchKeyUp: function(e) { var self = this; var value = $(e.currentTarget).val().trim(); + var ignoreKeys = [13, 27, 16, 17, 18, 91, 93, 37, 38, 39, 40, + 33, 34, 35, 36, 9, 20, 144, 145, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123]; - // List of keys to ignore (navigation, modifiers, etc.) - var ignoreKeys = [ - 13, // Enter (handled separately) - 27, // Escape (handled separately) - 16, 17, 18, // Shift, Ctrl, Alt - 91, 93, // Command keys (Mac) - 37, 38, 39, 40, // Arrow keys - 33, 34, 35, 36, // Page Up/Down, Home, End - 9, // Tab - 20, // Caps Lock - 144, // Num Lock - 145, // Scroll Lock - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123 // F1-F12 - ]; - - // Ignore if it's a navigation or modifier key - if (ignoreKeys.indexOf(e.which) !== -1) { - return; - } - - // Check if the actual value changed - if (value === this._lastSearchValue) { - return; - } + if (ignoreKeys.indexOf(e.which) !== -1) return; + if (value === this._lastSearchValue) return; this._lastSearchValue = value; - // Clear previous timer if (this._searchTimer) { clearTimeout(this._searchTimer); } - // Show loading after short delay this._searchTimer = setTimeout(function() { - // Delegate to controller if (self.getParent() && self.getParent()._handleCustomSearch) { self.getParent()._handleCustomSearch(value); } }, 500); }, - /** - * Handle special keys - */ _onCustomSearchKeyDown: function(e) { - // Enter key - trigger search immediately if (e.which === 13) { e.preventDefault(); if (this._searchTimer) { @@ -748,26 +493,19 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { if (this.getParent() && this.getParent()._handleCustomSearch) { this.getParent()._handleCustomSearch(value); } - } - // Escape key - clear search - else if (e.which === 27) { + } else if (e.which === 27) { e.preventDefault(); this._onCustomClearSearch(); } }, - /** - * Handle clear button click - */ _onCustomClearSearch: function() { - // Clear input this.$('.oe_search_input').val('').focus(); this.$('.oe_clear_search').hide(); this.$('.oe_search_count').hide(); this._lastInputValue = ''; this._lastSearchValue = ''; - // Delegate to controller if (this.getParent() && this.getParent()._clearCustomSearch) { this.getParent()._clearCustomSearch(); }