Optimize and clean code: Remove RTL/language specific logic, simplify search implementation

This commit is contained in:
Mohamed Eltayar 2025-08-30 15:41:23 +03:00
parent 1c52a0b38a
commit 8aba15b064
1 changed files with 72 additions and 334 deletions

View File

@ -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 =
'<div class="oe_search_container" dir="' + dir + '" style="display: flex; align-items: center; margin: 8px; background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); padding: 12px; border-radius: 6px; border: 1px solid #dee2e6; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">' +
'<input type="text" class="oe_search_input form-control" ' +
'placeholder="' + _.escape(placeholder) + '" ' +
'<div class="oe_search_container d-flex align-items-center">' +
'<input type="text" class="oe_search_input form-control flex-grow-1" ' +
'placeholder="' + _.escape(_t('Search in all visible columns...')) + '" ' +
'value="' + _.escape(savedValue) + '" ' +
'dir="' + dir + '" ' +
'style="flex: 1; border: 1px solid #ced4da; height: 36px; padding: 0 12px; border-radius: 4px; font-size: 14px;" ' +
'autocomplete="off">' +
'<button class="btn btn-secondary oe_clear_search ml-2" style="display: ' + (savedValue ? 'inline-block' : 'none') + '; min-width: 70px;">' +
'<i class="fa fa-times mr-1"></i>' + clearText +
'<button class="btn btn-secondary oe_clear_search ml-2" style="display: ' +
(savedValue ? 'inline-block' : 'none') + ';">' +
'<i class="fa fa-times mr-1"></i>' + _t('Clear') +
'</button>' +
'<span class="oe_search_count badge badge-success ml-2" style="display: ' + (isFiltered ? 'inline-block' : 'none') + '; padding: 6px 10px; font-size: 0.9em;">' +
'<span class="oe_search_count badge badge-success ml-2" style="display: ' +
(isFiltered ? 'inline-block' : 'none') + ';">' +
countText +
'</span>' +
'<span class="oe_search_loading ml-2" style="display: none; color: #007bff;">' +
'<span class="oe_search_loading ml-2" style="display: none;">' +
'<i class="fa fa-spinner fa-spin"></i>' +
'</span>' +
'</div>';
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();
}