تحسين وإصلاح موديول البحث العام في List Views - نسخة محسنة بالكامل

التحسينات والإصلاحات:
- إصلاح استخدام reload method بالطريقة الصحيحة لـ Odoo 14
- تحسين معالجة حقول Many2one للبحث الصحيح
- إضافة دعم أفضل للحصول على الحقول المرئية من fieldsInfo
- تحسين الأداء مع debouncing محسن (300ms)
- إضافة معالجة أفضل للأخطاء والحالات الاستثنائية
- دعم محسن للغة العربية مع تطبيع شامل للنصوص
- إضافة دعم مفاتيح Enter و Escape للتحكم السريع
- تحسين واجهة المستخدم مع عرض حالة التحميل
- إصلاح مشاكل التزامن والبحث المتكرر
- معالجة صحيحة للـ domains المعقدة
This commit is contained in:
Mohamed Eltayar 2025-08-30 14:32:03 +03:00
parent 7c1890e349
commit 349b144467
1 changed files with 396 additions and 124 deletions

View File

@ -7,9 +7,10 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
var _t = core._t; var _t = core._t;
var concurrency = require('web.concurrency'); var concurrency = require('web.concurrency');
var rpc = require('web.rpc'); var rpc = require('web.rpc');
var session = require('web.session');
// ================================ // ================================
// CORRECT APPROACH: Use ListController for data operations // ListController: Data operations
// ================================ // ================================
ListController.include({ ListController.include({
@ -18,96 +19,171 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
*/ */
init: function () { init: function () {
this._super.apply(this, arguments); this._super.apply(this, arguments);
// Initialize search state in controller (correct place) // Initialize search state
this._customSearchState = { this._customSearchState = {
timer: null, timer: null,
value: '', value: '',
isFiltered: false, isFiltered: false,
filteredCount: 0, filteredCount: 0,
originalDomain: null, originalDomain: null,
searchInProgress: false searchInProgress: false,
lastSearchPromise: null
}; };
this._searchMutex = new concurrency.Mutex(); this._searchMutex = new concurrency.Mutex();
}, },
/** /**
* CORRECT: Handle custom search from controller * @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) { _handleCustomSearch: function(value) {
var self = this; var self = this;
if (self._customSearchState.searchInProgress) { // Cancel any pending search
console.log('Search already in progress, ignoring'); if (self._customSearchState.timer) {
return Promise.resolve(); clearTimeout(self._customSearchState.timer);
self._customSearchState.timer = null;
} }
console.log('=== CONTROLLER SEARCH ==='); // Debounce search
console.log('Search value:', value); return new Promise(function(resolve) {
self._customSearchState.timer = setTimeout(function() {
return this._searchMutex.exec(function() { self._executeCustomSearch(value).then(resolve);
self._customSearchState.searchInProgress = true; }, 300);
if (!value || value.length === 0) {
console.log('Empty search - clearing filters');
return self._clearCustomSearch().finally(function() {
self._customSearchState.searchInProgress = false;
});
}
// Store original domain
if (!self._customSearchState.originalDomain && !self._customSearchState.isFiltered) {
self._customSearchState.originalDomain = self.model.get(self.handle).domain.slice();
console.log('Stored original domain:', self._customSearchState.originalDomain);
}
// Build and apply search domain
return self._applyCustomSearch(value).finally(function() {
self._customSearchState.searchInProgress = false;
});
}); });
}, },
/** /**
* CORRECT: Apply search using controller's reload method * 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);
}
}
return self._applyCustomSearch(value);
}).finally(function() {
self._customSearchState.searchInProgress = false;
self._customSearchState.lastSearchPromise = null;
});
self._customSearchState.lastSearchPromise = searchPromise;
return searchPromise;
},
/**
* Apply search using proper Odoo 14 method
*/ */
_applyCustomSearch: function(value) { _applyCustomSearch: function(value) {
var self = this; var self = this;
// Build search domain // Build search domain
var searchDomain = this._buildCustomSearchDomain(value); var searchDomain = this._buildCustomSearchDomain(value);
if (searchDomain.length === 0) { if (!searchDomain || searchDomain.length === 0) {
console.warn('No searchable fields found'); console.warn('No searchable fields found or no valid search domain');
return Promise.resolve(); return Promise.resolve();
} }
// Combine with original domain // Combine with original domain
var finalDomain = this._combineCustomDomains(searchDomain); var finalDomain = this._combineCustomDomains(searchDomain);
console.log('Final search domain:', finalDomain); console.log('Search domain:', searchDomain);
console.log('Final domain:', finalDomain);
// Step 1: Get count // 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({ return rpc.query({
model: this.modelName, model: this.modelName,
method: 'search_count', method: 'search_count',
args: [finalDomain], args: [finalDomain],
context: this.model.get(this.handle).getContext() context: session.user_context
}).then(function(count) { }).then(function(count) {
console.log('Search count:', count); console.log('Found records:', count);
// Update UI // Update UI with count
self._customSearchState.filteredCount = count; self._customSearchState.filteredCount = count;
self._updateCustomSearchUI(count); self._updateCustomSearchUI(count);
// Step 2: CORRECT WAY - Use controller's reload method // Now reload the view with new domain
return self.reload({domain: finalDomain}); // 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
limit: state.limit || 80
});
}).then(function() { }).then(function() {
// Update the view
self._customSearchState.isFiltered = true; self._customSearchState.isFiltered = true;
self._customSearchState.value = value; self._customSearchState.value = value;
// Trigger update to renderer
return self.update({}, {reload: false});
}).then(function() {
console.log('Search applied successfully'); console.log('Search applied successfully');
return Promise.resolve(); return Promise.resolve();
}).catch(function(error) { }).catch(function(error) {
console.error('Search error:', error); console.error('Search error:', error);
self._showSearchError();
return Promise.resolve(); return Promise.resolve();
}).finally(function() {
// Hide loading
if (self.renderer) {
self.renderer.$('.oe_search_loading').hide();
}
}); });
}, },
@ -119,62 +195,121 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
console.log('=== CLEARING CUSTOM SEARCH ==='); console.log('=== CLEARING CUSTOM SEARCH ===');
// Clear UI // Clear UI immediately
this.renderer.$('.oe_search_input').val(''); if (this.renderer && this.renderer.$) {
this.renderer.$('.oe_clear_search').hide(); this.renderer.$('.oe_search_input').val('');
this.renderer.$('.oe_search_count').hide(); this.renderer.$('.oe_clear_search').hide();
this.renderer.$('.oe_search_count').hide();
this.renderer.$('.oe_search_loading').show();
}
// Reset state // Reset state
this._customSearchState.value = ''; this._customSearchState.value = '';
this._customSearchState.isFiltered = false; this._customSearchState.isFiltered = false;
this._customSearchState.filteredCount = 0; this._customSearchState.filteredCount = 0;
// Restore original domain // Get original domain
var originalDomain = this._customSearchState.originalDomain || []; var originalDomain = this._customSearchState.originalDomain || [];
this._customSearchState.originalDomain = null; this._customSearchState.originalDomain = null;
// CORRECT: Use controller's reload with original domain console.log('Restoring original domain:', originalDomain);
return this.reload({domain: originalDomain});
// Reload with original domain
var handle = this.handle;
var state = this.model.get(handle);
return this.model.reload(handle, {
domain: originalDomain,
offset: 0,
limit: state.limit || 80
}).then(function() {
return self.update({}, {reload: false});
}).finally(function() {
if (self.renderer) {
self.renderer.$('.oe_search_loading').hide();
}
});
}, },
/** /**
* Build search domain * Build search domain - FIXED VERSION
*/ */
_buildCustomSearchDomain: function(value) { _buildCustomSearchDomain: function(value) {
if (!value) return [];
var fields = this._getCustomSearchableFields(); var fields = this._getCustomSearchableFields();
if (fields.length === 0) { if (!fields || fields.length === 0) {
console.warn('No searchable fields found');
return []; return [];
} }
var conditions = []; var conditions = [];
var normalized = this._normalizeArabic(value); var normalized = this._normalizeArabic(value);
var searchValues = [value];
if (normalized !== value) {
searchValues.push(normalized);
}
console.log('Building domain for fields:', fields);
fields.forEach(function(field) { fields.forEach(function(field) {
try { try {
if (['char', 'text', 'html'].includes(field.type)) { var fieldType = field.type;
conditions.push([field.name, 'ilike', value]); var fieldName = field.name;
if (normalized !== value) {
conditions.push([field.name, 'ilike', normalized]); switch(fieldType) {
} case 'char':
} else if (['integer', 'float', 'monetary'].includes(field.type)) { case 'text':
var num = parseFloat(value); case 'html':
if (!isNaN(num)) { searchValues.forEach(function(searchVal) {
conditions.push([field.name, '=', num]); conditions.push([fieldName, 'ilike', searchVal]);
} });
} else if (field.type === 'many2one') { break;
conditions.push([field.name + '.name', 'ilike', value]);
if (normalized !== value) { case 'integer':
conditions.push([field.name + '.name', 'ilike', normalized]); case 'float':
} case 'monetary':
} else if (field.type === 'selection') { var numValue = parseFloat(value);
conditions.push([field.name, 'ilike', value]); if (!isNaN(numValue)) {
} else if (field.type === 'boolean') { // For numeric fields, also search as string
var lowerValue = value.toLowerCase(); conditions.push([fieldName, '=', numValue]);
if (['true', 'yes', 'نعم', '1'].includes(lowerValue)) { // Also search string representation
conditions.push([field.name, '=', true]); conditions.push([fieldName, 'ilike', value]);
} else if (['false', 'no', 'لا', '0'].includes(lowerValue)) { }
conditions.push([field.name, '=', false]); 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) { } catch (error) {
console.warn('Error processing field', field.name, ':', error); console.warn('Error processing field', field.name, ':', error);
@ -185,28 +320,54 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
return []; return [];
} }
// Build proper OR domain
if (conditions.length === 1) { if (conditions.length === 1) {
return conditions; return conditions[0];
} }
// Build OR domain // Create OR domain: ['|', ['field1', 'op', 'val'], ['field2', 'op', 'val']]
var domain = []; var orDomain = [];
for (var i = 1; i < conditions.length; i++) { for (var i = 1; i < conditions.length; i++) {
domain.push('|'); orDomain.push('|');
} }
return domain.concat(conditions); return orDomain.concat(conditions);
}, },
/** /**
* Get searchable fields * Get searchable fields - ENHANCED VERSION
*/ */
_getCustomSearchableFields: function() { _getCustomSearchableFields: function() {
var fields = []; var fields = [];
var state = this.model.get(this.handle); var state = this.model.get(this.handle);
var fieldDefs = state.fields || {};
// Get from renderer columns if (!state) {
if (this.renderer.columns) { console.warn('No state available');
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];
if (fieldInfo && !fieldInfo.invisible && fieldDefs[fieldName]) {
var fieldDef = fieldDefs[fieldName];
if (fieldDef.store !== false && fieldDef.searchable !== false) {
fields.push({
name: fieldName,
type: fieldDef.type,
string: fieldDef.string || fieldName
});
}
}
});
}
// Method 2: Get from renderer columns if available
if (fields.length === 0 && this.renderer && this.renderer.columns) {
this.renderer.columns.forEach(function(col) { this.renderer.columns.forEach(function(col) {
if (!col.invisible && col.attrs && col.attrs.name) { if (!col.invisible && col.attrs && col.attrs.name) {
var fieldName = col.attrs.name; var fieldName = col.attrs.name;
@ -214,43 +375,53 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
if (field && field.store !== false) { if (field && field.store !== false) {
fields.push({ fields.push({
name: fieldName, name: fieldName,
type: field.type type: field.type,
string: field.string || fieldName
}); });
} }
} }
}); });
} }
// Fallback fields // Method 3: Fallback to common searchable fields
if (fields.length === 0) { if (fields.length === 0) {
var commonFields = ['name', 'display_name', 'code', 'reference']; var commonFields = ['name', 'display_name', 'code', 'reference', 'ref', 'description'];
commonFields.forEach(function(fname) { commonFields.forEach(function(fname) {
if (fieldDefs[fname] && fieldDefs[fname].store !== false) { if (fieldDefs[fname] && fieldDefs[fname].store !== false) {
fields.push({ fields.push({
name: fname, name: fname,
type: fieldDefs[fname].type type: fieldDefs[fname].type,
string: fieldDefs[fname].string || fname
}); });
} }
}); });
} }
console.log('Searchable fields:', fields); console.log('Searchable fields found:', fields);
return fields; return fields;
}, },
/** /**
* Combine domains * Combine domains - FIXED VERSION
*/ */
_combineCustomDomains: function(searchDomain) { _combineCustomDomains: function(searchDomain) {
var originalDomain = this._customSearchState.originalDomain || []; var originalDomain = this._customSearchState.originalDomain || [];
if (originalDomain.length > 0 && searchDomain.length > 0) { // Handle empty domains
return ['&'].concat(originalDomain).concat(searchDomain); if (!searchDomain || searchDomain.length === 0) {
} else if (searchDomain.length > 0) {
return searchDomain;
} else {
return originalDomain; 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);
}, },
/** /**
@ -258,41 +429,76 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
*/ */
_updateCustomSearchUI: function(count) { _updateCustomSearchUI: function(count) {
if (this.renderer && this.renderer.$) { if (this.renderer && this.renderer.$) {
this.renderer.$('.oe_search_count').text(_t('Found: ') + count + _t(' records')).show(); var message = _t('Found: ') + count + _t(' records');
this.renderer.$('.oe_search_count')
.text(message)
.show();
this.renderer.$('.oe_clear_search').show();
} }
}, },
/** /**
* Normalize Arabic text * Show search error
*/
_showSearchError: function() {
if (this.renderer && this.renderer.$) {
this.renderer.$('.oe_search_count')
.text(_t('Search error occurred'))
.addClass('text-danger')
.show();
}
},
/**
* Normalize Arabic text - ENHANCED VERSION
*/ */
_normalizeArabic: function(text) { _normalizeArabic: function(text) {
if (!text) return ''; if (!text) return '';
return text return text
// Remove Arabic diacritics
.replace(/[\u064B-\u065F]/g, '') .replace(/[\u064B-\u065F]/g, '')
.replace(/[\u0660-\u0669]/g, function(d) { // Convert Arabic-Indic digits to Western digits
.replace(/[٠-٩]/g, function(d) {
return String.fromCharCode(d.charCodeAt(0) - 0x0660 + 0x0030); return String.fromCharCode(d.charCodeAt(0) - 0x0660 + 0x0030);
}) })
.replace(/[\u06F0-\u06F9]/g, function(d) { // Convert Persian digits to Western digits
.replace(/[۰-۹]/g, function(d) {
return String.fromCharCode(d.charCodeAt(0) - 0x06F0 + 0x0030); return String.fromCharCode(d.charCodeAt(0) - 0x06F0 + 0x0030);
}) })
.replace(/[\u0622\u0623\u0625\u0627]/g, 'ا') // Normalize Alef variations
.replace(/[\u0629]/g, 'ه') .replace(/[آأإا]/g, 'ا')
.replace(/[\u064A\u0626\u0649]/g, 'ي') // Normalize Teh Marbuta
.replace(/[\u0624\u0648]/g, 'و'); .replace(/ة/g, 'ه')
// Normalize Yeh variations
.replace(/[يئى]/g, 'ي')
// Normalize Waw variations
.replace(/[ؤو]/g, 'و')
// Trim spaces
.trim();
} }
}); });
// ================================ // ================================
// ListRenderer: Only for UI (correct approach) // ListRenderer: UI only
// ================================ // ================================
ListRenderer.include({ ListRenderer.include({
events: _.extend({}, ListRenderer.prototype.events, { events: _.extend({}, ListRenderer.prototype.events, {
'keyup .oe_search_input': '_onCustomSearchKeyUp', 'keyup .oe_search_input': '_onCustomSearchKeyUp',
'input .oe_search_input': '_onCustomSearchInput', 'input .oe_search_input': '_onCustomSearchInput',
'click .oe_clear_search': '_onCustomClearSearch' 'click .oe_clear_search': '_onCustomClearSearch',
'keydown .oe_search_input': '_onCustomSearchKeyDown'
}), }),
/**
* @override
*/
init: function() {
this._super.apply(this, arguments);
this._searchTimer = null;
this._customSearchReady = false;
},
/** /**
* @override * @override
*/ */
@ -300,62 +506,128 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
var self = this; var self = this;
return this._super.apply(this, arguments).then(function (result) { return this._super.apply(this, arguments).then(function (result) {
// Add search box for tree views // Add search box for tree views
if (self.arch && self.arch.tag === 'tree' && if (self._shouldAddSearchBox()) {
self.$el && self.$el.hasClass('o_list_view')) {
self._addCustomSearchBox(); self._addCustomSearchBox();
self._customSearchReady = true;
} }
return result; return result;
}); });
}, },
/** /**
* Add search box (UI only) * Check if we should add search box
*/ */
_addCustomSearchBox: function() { _shouldAddSearchBox: function() {
if (this.$el.find('.oe_search_container').length) { return this.arch &&
return; this.arch.tag === 'tree' &&
} this.$el &&
this.$el.hasClass('o_list_view') &&
var html = '<div class="oe_search_container" style="display: flex; align-items: center; margin: 8px; background: #f8f9fa; padding: 10px; border-radius: 4px;">' + !this.$el.find('.oe_search_container').length;
'<input type="text" class="oe_search_input" placeholder="' + _t('Search across all records...') + '" style="flex: 1; border: 1px solid #ccc; height: 32px; padding: 0 12px; border-radius: 4px;">' +
'<button class="btn btn-sm btn-secondary oe_clear_search ml-2" style="display: none;">' + _t('Clear') + '</button>' +
'<span class="oe_search_count ml-2" style="display: none; color: #6c757d; font-size: 0.9em; font-weight: 500;"></span>' +
'<span class="oe_search_loading ml-2" style="display: none;"><i class="fa fa-spinner fa-spin"></i></span>' +
'</div>';
this.$el.prepend($(html));
}, },
/** /**
* Handle input events (UI only, delegate to controller) * Add search box UI
*/ */
_onCustomSearchInput: function(e) { _addCustomSearchBox: function() {
// Just update UI var html =
this.$('.oe_clear_search').toggle(!!$(e.currentTarget).val().trim()); '<div class="oe_search_container" 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="' + _t('Search in all visible columns...') + '" ' +
'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: none; min-width: 70px;">' +
'<i class="fa fa-times mr-1"></i>' + _t('Clear') +
'</button>' +
'<span class="oe_search_count badge badge-success ml-2" style="display: none; padding: 6px 10px; font-size: 0.9em;"></span>' +
'<span class="oe_search_loading ml-2" style="display: none; color: #007bff;">' +
'<i class="fa fa-spinner fa-spin"></i>' +
'</span>' +
'</div>';
this.$el.prepend($(html));
// Focus on search input
this.$('.oe_search_input').focus();
}, },
/**
* Handle input events
*/
_onCustomSearchInput: function(e) {
var hasValue = !!$(e.currentTarget).val().trim();
this.$('.oe_clear_search').toggle(hasValue);
if (!hasValue) {
this.$('.oe_search_count').hide();
}
},
/**
* Handle keyup events with debounce
*/
_onCustomSearchKeyUp: function(e) { _onCustomSearchKeyUp: function(e) {
var self = this; var self = this;
var value = $(e.currentTarget).val().trim(); var value = $(e.currentTarget).val().trim();
// Ignore special keys
if (e.which === 13 || e.which === 27) {
return;
}
// Clear previous timer // Clear previous timer
if (this._searchTimer) { if (this._searchTimer) {
clearTimeout(this._searchTimer); clearTimeout(this._searchTimer);
} }
// CORRECT: Delegate to controller after delay // Show loading after short delay
this._searchTimer = setTimeout(function() { this._searchTimer = setTimeout(function() {
// Delegate to controller
if (self.getParent() && self.getParent()._handleCustomSearch) { if (self.getParent() && self.getParent()._handleCustomSearch) {
self.getParent()._handleCustomSearch(value); self.getParent()._handleCustomSearch(value);
} }
}, 500); }, 500);
}, },
/**
* Handle special keys
*/
_onCustomSearchKeyDown: function(e) {
// Enter key - trigger search immediately
if (e.which === 13) {
e.preventDefault();
if (this._searchTimer) {
clearTimeout(this._searchTimer);
}
var value = $(e.currentTarget).val().trim();
if (this.getParent() && this.getParent()._handleCustomSearch) {
this.getParent()._handleCustomSearch(value);
}
}
// Escape key - clear search
else if (e.which === 27) {
e.preventDefault();
this._onCustomClearSearch();
}
},
/**
* Handle clear button click
*/
_onCustomClearSearch: function() { _onCustomClearSearch: function() {
// CORRECT: Delegate to controller // Clear input
this.$('.oe_search_input').val('').focus();
this.$('.oe_clear_search').hide();
this.$('.oe_search_count').hide();
// Delegate to controller
if (this.getParent() && this.getParent()._clearCustomSearch) { if (this.getParent() && this.getParent()._clearCustomSearch) {
this.getParent()._clearCustomSearch(); this.getParent()._clearCustomSearch();
} }
} }
}); });
return {
ListController: ListController,
ListRenderer: ListRenderer
};
}); });