Enhanced JavaScript integration with Odoo 14 datepicker patterns

This commit is contained in:
Mohamed Eltayar 2025-08-28 16:46:07 +03:00
parent 1f759b0aa0
commit aaa8c7da30
1 changed files with 420 additions and 146 deletions

View File

@ -1,16 +1,31 @@
odoo.define('web_hijri_datepicker.datepicker', function (require) {
/**
* Enhanced Hijri Datepicker for Odoo 14
* Seamless integration with Odoo's standard datepicker system
* Maintains all Hijri functionality while following Odoo's patterns
*
* Features:
* - Full compatibility with Odoo 14 datepicker architecture
* - Preserved Hijri calendar conversion accuracy
* - Enhanced UI/UX matching Odoo's standard appearance
* - Improved performance and error handling
* - Better accessibility and RTL support
*/
odoo.define('web_hijri_datepicker.enhanced_datepicker', function (require) {
'use strict';
var core = require('web.core');
var datepicker = require('web.datepicker');
var field_utils = require('web.field_utils');
var time = require('web.time');
var FieldDate = require('web.basic_fields').FieldDate;
var session = require('web.session');
var _t = core._t;
// Enhanced Hijri month names with better Arabic support
// Enhanced Hijri month names with proper Arabic diacritics
var hijriMonths = {
"Muharram": "مُحَرَّم",
"Safar": "صَفَر",
"Safar": "صَفَر",
"Rabi' al-awwal": "رَبِيْعُ الأَوّل",
"Rabi' al-thani": "رَبِيْعُ الثَّانِي",
"Jumada al-awwal": "جَمَادِي الأَوّل",
@ -21,74 +36,118 @@ odoo.define('web_hijri_datepicker.datepicker', function (require) {
"Shawwal": "شَوَّال",
"Dhu al-Qi'dah": "ذُوالْقَعْدَة",
"Dhu al-Hijjah": "ذُوالْحِجَّة"
}
};
// Enhanced number conversion for better Arabic display
String.prototype.fromDigits = function () {
var arabicDigits = ['۰', '۱', '۲', '۳', '٤', '۵', '٦', '۷', '۸', '];
// Enhanced Arabic numeral conversion with better Unicode support
String.prototype.toArabicDigits = function () {
var arabicDigits = ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '];
return this.replace(/[0-9]/g, function (digit) {
return arabicDigits[+digit];
});
}
};
/**
* Enhanced DateWidget with improved Odoo integration
* Maintains all existing Hijri functionality while improving performance
*/
datepicker.DateWidget.include({
/**
* Initialize the enhanced datepicker with Odoo-compatible settings
*/
start: function () {
var self = this;
var def = this._super.apply(this, arguments);
// Get input elements
this.$input = this.$('input.o_datepicker_input');
this.$input_hijri = this.$('input.o_hijri');
// Enhanced click handler with better UX
this.$input_hijri.click(function (e) {
if (!this.$input_hijri.length) {
return def;
}
// Enhanced click handler with proper event delegation
this.$input_hijri.on('click.hijri_picker', function (e) {
e.preventDefault();
e.stopPropagation();
self.$input_hijri.calendarsPicker('show');
self._showHijriPicker();
});
// Focus handler for better UX
this.$input_hijri.on('focus.hijri_picker', function (e) {
if (!$(this).prop('readonly')) {
self._showHijriPicker();
}
});
// Initialize Hijri picker with enhanced configuration
this._initializeHijriPicker();
// Modern configuration with enhanced features
this.$input_hijri.calendarsPicker({
calendar: $.calendars.instance('islamic', this.options.locale || 'ar'),
return def;
},
/**
* Initialize Hijri calendar picker with Odoo-compatible styling
*/
_initializeHijriPicker: function () {
var self = this;
// Determine locale from Odoo session
var locale = session.user_context.lang || 'ar';
var isRTL = ['ar', 'fa', 'he'].indexOf(locale.split('_')[0]) !== -1;
// Enhanced picker configuration matching Odoo's datepicker behavior
var pickerConfig = {
calendar: $.calendars.instance('islamic', 'ar'),
dateFormat: 'M d, yyyy',
showAnim: 'slideDown', // Modern animation
showAnim: '', // Disable animation for consistency with Odoo
showSpeed: 'fast',
showOnFocus: false,
closeOnDateSelect: true, // Better UX - close after selection
yearRange: 'c-100:c+50', // Extended year range
closeOnDateSelect: true,
yearRange: 'c-100:c+50',
changeMonth: true,
changeYear: true,
showOtherMonths: true,
selectOtherMonths: true,
localNumbers: true, // Enable Arabic numerals
renderer: this._getModernRenderer(),
onSelect: this._convertDateToHijri.bind(this),
onShow: function() {
// Add modern CSS class for styling
$('.calendars-popup').addClass('hijri-modern-popup');
}
});
this.__libInput++;
this.$el.datetimepicker(this.options);
this.__libInput--;
this._setReadonly(false);
localNumbers: true,
renderer: this._getOdooCompatibleRenderer(),
onSelect: this._onHijriDateSelect.bind(this),
onShow: this._onHijriPickerShow.bind(this),
onClose: this._onHijriPickerClose.bind(this),
beforeShow: this._beforeHijriPickerShow.bind(this)
};
// Apply RTL configuration if needed
if (isRTL) {
pickerConfig.isRTL = true;
}
// Initialize the picker
try {
this.$input_hijri.calendarsPicker(pickerConfig);
} catch (error) {
console.warn('Hijri datepicker initialization error:', error);
}
},
/**
* Get modern renderer configuration for better styling
* Get renderer configuration that matches Odoo's datepicker appearance
*/
_getModernRenderer: function() {
_getOdooCompatibleRenderer: function () {
return $.extend({}, $.calendarsPicker.defaultRenderer, {
picker: '<div class="calendars">{months}</div>',
monthRow: '<div class="calendars-month-row">{months}</div>',
month: '<div class="calendars-month">' +
'<div class="calendars-month-header">' +
'<button type="button" class="calendars-nav calendars-prev" title="{prevText}">{prevText}</button>' +
'<div class="calendars-month-year">{monthHeader}</div>' +
'<button type="button" class="calendars-nav calendars-next" title="{nextText}">{nextText}</button>' +
picker: '<div class=\"calendars bootstrap-datetimepicker-widget\">{months}</div>',
monthRow: '<div class=\"calendars-month-row\">{months}</div>',
month: '<div class=\"calendars-month\">' +
'<div class=\"calendars-month-header\">' +
'<button type=\"button\" class=\"calendars-nav calendars-prev\" title=\"{prevText}\"></button>' +
'<div class=\"calendars-month-year\">{monthHeader}</div>' +
'<button type=\"button\" class=\"calendars-nav calendars-next\" title=\"{nextText}\"></button>' +
'</div>' +
'<table><thead>{weekHeader}</thead><tbody>{weeks}</tbody></table>' +
'<table class=\"table-sm\"><thead>{weekHeader}</thead><tbody>{weeks}</tbody></table>' +
'</div>',
weekHeader: '<tr>{days}</tr>',
dayHeader: '<th>{day}</th>',
dayHeader: '<th scope=\"col\">{day}</th>',
week: '<tr>{days}</tr>',
day: '<td>{day}</td>',
monthSelector: '.calendars-month',
@ -101,160 +160,368 @@ odoo.define('web_hijri_datepicker.datepicker', function (require) {
todayClass: 'calendars-today',
otherMonthClass: 'calendars-other-month',
weekendClass: 'calendars-weekend',
commandClass: 'calendars-cmd calendars-cmd-',
commandButtonClass: '',
commandClass: 'calendars-cmd',
commandButtonClass: 'btn btn-sm',
commandLinkClass: '',
disabledClass: 'calendars-disabled'
});
},
_convertGregorianToHijri: function (date) {
var year, month, day, jd, formatted_date;
var calendar = $.calendars.instance('islamic');
if (date && !_.isUndefined(date)) {
date = moment(date).locale('en');
month = parseInt(date.format('M'));
day = parseInt(date.format('D'));
year = parseInt(date.format('YYYY'));
jd = $.calendars.instance('gregorian').toJD(year, month, day);
formatted_date = calendar.fromJD(jd);
var hijriMonth = calendar.formatDate('MM', formatted_date);
var hijriDate = calendar.formatDate('d, yyyy', formatted_date);
// Enhanced Arabic localization
if (this.options.locale === 'ar' || !this.options.locale) {
hijriDate = hijriDate.fromDigits();
// Find Arabic month name
var arabicMonth = _.find(hijriMonths, function (value, key) {
return key === hijriMonth;
});
hijriMonth = arabicMonth || hijriMonth;
}
return _.str.sprintf("%s %s", hijriMonth, hijriDate);
/**
* Show Hijri picker with proper positioning
*/
_showHijriPicker: function () {
if (this.$input_hijri.hasClass('hasCalendarsPicker')) {
this.$input_hijri.calendarsPicker('show');
}
},
_convertDateToHijri: function (date) {
if (!date || date.length === 0) {
return false;
/**
* Handle picker show event with Odoo-compatible styling
*/
_onHijriPickerShow: function (picker) {
var $popup = $('.calendars-popup');
if ($popup.length) {
// Apply Odoo-compatible classes and positioning
$popup.addClass('dropdown-menu show');
$popup.css('z-index', 1051); // Same as Odoo datepicker
// Position relative to the input field
this._positionHijriPicker($popup);
}
},
/**
* Position the Hijri picker popup similar to Odoo's datepicker
*/
_positionHijriPicker: function ($popup) {
var $input = this.$input_hijri;
var inputOffset = $input.offset();
var inputHeight = $input.outerHeight();
var popupHeight = $popup.outerHeight();
var windowHeight = $(window).height();
var scrollTop = $(window).scrollTop();
// Calculate optimal position
var topPosition = inputOffset.top + inputHeight + 5;
// Check if there's enough space below, otherwise show above
if ((topPosition + popupHeight) > (windowHeight + scrollTop)) {
topPosition = inputOffset.top - popupHeight - 5;
}
// Prevent event bubbling for better UX
$popup.css({
'position': 'absolute',
'top': topPosition + 'px',
'left': inputOffset.left + 'px',
'right': 'auto',
'bottom': 'auto'
});
},
/**
* Handle picker close event
*/
_onHijriPickerClose: function (picker) {
// Remove Odoo classes when closing
$('.calendars-popup').removeClass('dropdown-menu show');
},
/**
* Before show event handler for validation
*/
_beforeHijriPickerShow: function (picker) {
// Validate current date and sync with Gregorian if needed
this._syncDatesBeforeShow();
return true;
},
/**
* Sync dates before showing picker to ensure consistency
*/
_syncDatesBeforeShow: function () {
if (this.$input.val() && !this.$input_hijri.val()) {
// Convert Gregorian to Hijri if Hijri is empty
var gregorianDate = this.$input.val();
var hijriDate = this._convertGregorianToHijri(gregorianDate);
if (hijriDate) {
this.$input_hijri.val(hijriDate);
}
}
},
/**
* Enhanced Gregorian to Hijri conversion with better error handling
*/
_convertGregorianToHijri: function (date) {
if (!date || _.isUndefined(date)) {
return null;
}
try {
var calendar = $.calendars.instance('islamic');
var momentDate = moment(date, ['YYYY-MM-DD', 'DD/MM/YYYY', 'MM/DD/YYYY'], true);
if (!momentDate.isValid()) {
return null;
}
var year = momentDate.year();
var month = momentDate.month() + 1; // moment months are 0-based
var day = momentDate.date();
var jd = $.calendars.instance('gregorian').toJD(year, month, day);
var hijriDate = calendar.fromJD(jd);
var hijriMonthName = calendar.formatDate('MM', hijriDate);
var hijriDay = calendar.formatDate('d', hijriDate);
var hijriYear = calendar.formatDate('yyyy', hijriDate);
// Get Arabic month name
var arabicMonth = hijriMonths[hijriMonthName] || hijriMonthName;
// Format with Arabic numerals if locale supports it
var locale = session.user_context.lang || 'ar';
if (locale.startsWith('ar')) {
hijriDay = hijriDay.toString().toArabicDigits();
hijriYear = hijriYear.toString().toArabicDigits();
}
return arabicMonth + ' ' + hijriDay + '، ' + hijriYear;
} catch (error) {
console.warn('Hijri conversion error:', error);
return null;
}
},
/**
* Enhanced Hijri date selection handler with improved validation
*/
_onHijriDateSelect: function (dates) {
if (!dates || dates.length === 0) {
return false;
}
// Prevent event bubbling
$(document).off('click.calendars').on('click.calendars', '.calendars a', function (e) {
e.preventDefault();
e.stopImmediatePropagation();
return false;
});
try {
var selectedDate = date[0];
var jd = $.calendars.instance('islamic').toJD(
parseInt(selectedDate.year()),
parseInt(selectedDate.month()),
parseInt(selectedDate.day())
);
var selectedDate = dates[0];
var hijriYear = selectedDate.year();
var hijriMonth = selectedDate.month();
var hijriDay = selectedDate.day();
// Convert to Gregorian
var calendar = $.calendars.instance('islamic');
var jd = calendar.toJD(hijriYear, hijriMonth, hijriDay);
var gregorianDate = $.calendars.instance('gregorian').fromJD(jd);
var dateValue = moment(time.str_to_date(gregorianDate)).add(1, 'days');
this.setValue(this._parseClient(dateValue));
this.trigger("datetime_changed");
// Create moment object for Odoo compatibility
var gregorianMoment = moment([
gregorianDate.year(),
gregorianDate.month() - 1, // moment months are 0-based
gregorianDate.day()
]);
// Hide the popup after selection for better UX
// Update Gregorian input using Odoo's standard format
var formattedDate = gregorianMoment.format('YYYY-MM-DD');
this.$input.val(formattedDate);
// Trigger Odoo's change events
this.$input.trigger('change');
this.trigger_up('field_changed', {
dataPointID: this.dataPointID,
changes: {}
});
// Update the widget's value if this is part of a field
if (this.setValue && typeof this.setValue === 'function') {
this.setValue(this._parseValue(gregorianMoment));
}
// Hide picker after selection
this.$input_hijri.calendarsPicker('hide');
} catch (error) {
console.warn('Hijri date conversion error:', error);
console.error('Hijri date selection error:', error);
// Show user-friendly error message
this._showDateConversionError();
}
},
_parseDate: function (v) {
return v.clone().locale('en').format('YYYY-MM-DD');
/**
* Show user-friendly error message for date conversion issues
*/
_showDateConversionError: function () {
if (this.displayNotification) {
this.displayNotification({
type: 'warning',
title: _t('Date Conversion Warning'),
message: _t('Unable to convert the selected Hijri date. Please try again.'),
sticky: false
});
}
},
/**
* Enhanced setValue method with better Hijri synchronization
*/
setValue: function (value) {
this._super.apply(this, arguments);
var parsed_date = value ? this._parseDate(value) : null;
var hijri_value = parsed_date ? this._convertGregorianToHijri(parsed_date) : null;
// Enhanced placeholder handling
if (hijri_value) {
this.$input_hijri.val(hijri_value).removeClass('o_input_placeholder');
if (!this.$input_hijri) {
return;
}
// Convert and set Hijri value
var parsedDate = value ? this._parseDate(value) : null;
var hijriValue = parsedDate ? this._convertGregorianToHijri(parsedDate) : null;
// Update Hijri input with proper styling
if (hijriValue) {
this.$input_hijri.val(hijriValue)
.removeClass('o_input_placeholder')
.addClass('text-success');
} else {
this.$input_hijri.val('').addClass('o_input_placeholder');
this.$input_hijri.val('')
.addClass('o_input_placeholder')
.removeClass('text-success');
}
},
/**
* Parse date value maintaining Odoo compatibility
*/
_parseDate: function (value) {
if (!value) return null;
var momentValue = moment.isMoment(value) ? value : moment(value);
return momentValue.isValid() ? momentValue.locale('en').format('YYYY-MM-DD') : null;
},
/**
* Parse client value for Odoo compatibility
*/
_parseValue: function (value) {
if (!value) return false;
var momentValue = moment.isMoment(value) ? value : moment(value);
return momentValue.isValid() ? momentValue : false;
},
/**
* Format client value for display
*/
_formatClient: function (value) {
return field_utils.format[this.type_of_date || 'date'](value, null, {timezone: false});
},
/**
* Enhanced cleanup with proper event removal
*/
destroy: function () {
// Clean up event handlers
// Clean up Hijri picker events
if (this.$input_hijri) {
this.$input_hijri.off('.hijri_picker');
if (this.$input_hijri.hasClass('hasCalendarsPicker')) {
try {
this.$input_hijri.calendarsPicker('destroy');
} catch (e) {
console.warn('Error destroying Hijri picker:', e);
}
}
}
// Clean up global events
$(document).off('click.calendars');
if (this.$input_hijri && this.$input_hijri.hasClass('hasCalendarsPicker')) {
this.$input_hijri.calendarsPicker('destroy');
}
if (this.$el) {
this.__libInput++;
this.$el.datetimepicker('destroy');
this.__libInput--;
}
this._super.apply(this, arguments);
},
/**
* Handle input click events properly
*/
_onInputClicked: function (e) {
if (e && e.target && !$(e.target).hasClass('o_hijri')) {
return this._super();
if (e && e.target && $(e.target).hasClass('o_hijri')) {
// Let Hijri picker handle this
return;
}
},
_formatClients: function (v) {
return field_utils.format[this.type_of_date](v, null, {timezone: true});
},
return this._super.apply(this, arguments);
}
});
/**
* Enhanced FieldDate for better readonly display
*/
FieldDate.include({
/**
* Enhanced readonly rendering with improved Hijri display
*/
_renderReadonly: function () {
var self = this;
this._super.apply(this, arguments);
if (!this.value) {
return;
}
// Create date widget for conversion
this.datewidget = this.datewidget || this._makeDatePicker();
if (this.value) {
this.datewidget = this._makeDatePicker();
var $container = $('<div/>');
// Gregorian date
var gregorianValue = this.value ? this.datewidget._formatClients(this.value) : '';
var $gregorianDiv = $('<div>', {
class: this.$el.attr('class') + ' o_gregorian_date',
text: gregorianValue,
if (!this.datewidget._convertGregorianToHijri) {
return; // Skip if Hijri conversion is not available
}
var $container = $('<div class="o_hijri_readonly_container"/>');
// Gregorian date display
var gregorianValue = this._formatValue(this.value);
var $gregorianDiv = $('<div>', {
class: 'o_gregorian_date',
text: gregorianValue
});
// Hijri date display with enhanced styling
var parsedDate = this.datewidget._parseDate(this.value);
var hijriValue = parsedDate ? this.datewidget._convertGregorianToHijri(parsedDate) : null;
if (hijriValue) {
var $hijriDiv = $('<div>', {
class: 'o_hijri_date'
});
// Hijri date with enhanced styling
var parsed_date = this.value ? this.datewidget._parseDate(this.value) : '';
var hijri_value = parsed_date ? this.datewidget._convertGregorianToHijri(parsed_date) : '';
if (hijri_value) {
var $hijriDiv = $('<div>', {
class: this.$el.attr('class') + ' o_hijri_date mt-1',
style: 'font-size: 12px; color: #28a745; font-weight: 500;'
});
$hijriDiv.html('<span style="color: #6c757d;">التاريخ الهجري:</span> ' + hijri_value);
$container.append($gregorianDiv).append($hijriDiv);
} else {
$container.append($gregorianDiv);
}
var $hijriLabel = $('<span>', {
text: _t('Hijri Date: '),
style: 'color: #6c757d; font-weight: 400;'
});
var $hijriValue = $('<span>', {
text: hijriValue,
style: 'color: #28a745; font-weight: 500;'
});
$hijriDiv.append($hijriLabel).append($hijriValue);
$container.append($gregorianDiv).append($hijriDiv);
} else {
$container.append($gregorianDiv);
}
// Replace the element content
if (this.datewidget) {
this.datewidget.appendTo('<div>').then(function () {
self._replaceElement($container);
});
}
},
}
});
// Enhanced integration with Odoo's form validation
// Enhanced validator for Hijri dates
if ($.validator) {
$.validator.addMethod('hijriDate', function(value, element) {
if (!value) return true;
@ -262,23 +529,30 @@ odoo.define('web_hijri_datepicker.datepicker', function (require) {
try {
var calendar = $.calendars.instance('islamic');
var parsedDate = calendar.parseDate('M d, yyyy', value);
return parsedDate && calendar.isValid(parsedDate.year(), parsedDate.month(), parsedDate.day());
return parsedDate && calendar.isValid(
parsedDate.year(),
parsedDate.month(),
parsedDate.day()
);
} catch (e) {
return false;
}
}, 'الرجاء إدخال تاريخ هجري صحيح');
}, _t('Please enter a valid Hijri date'));
}
// Global configuration for better performance
// Global configuration for consistent behavior
if ($.calendarsPicker) {
$.calendarsPicker.setDefaults({
showSpeed: 'fast',
showAnim: 'slideDown',
showAnim: '',
changeMonth: true,
changeYear: true,
localNumbers: true,
yearRange: 'c-100:c+50'
yearRange: 'c-100:c+50',
closeOnDateSelect: true
});
}
});
console.log('Enhanced Hijri Datepicker loaded successfully');
});