// Function to configure and start observing a table function observeTable(tableId, debugMode = false) { var targetNode = document.querySelector(tableId + ' tbody'); if (!targetNode) { if (debugMode) { console.error('Table with ID ' + tableId + ' not found.'); } return; } $(tableId + ' tbody tr').each(function() { cloneAndModifyElement($(this), debugMode); }); var observer = new MutationObserver(function(mutationsList, observer) { mutationsList.forEach(function(mutation) { if (mutation.type === 'childList' && mutation.target.nodeName.toLowerCase() === 'tbody') { mutation.addedNodes.forEach(function(node) { if ($(node).is('tr') && !$(node).hasClass('is-cloned') && !$(node).hasClass('cloned-element')) { if (debugMode) { console.log('New element detected in ' + tableId + '.'); console.log('Inserted node:', node); } var originalRow = $(node); disconnectObserver(); cloneAndModifyElement($(node), debugMode); connectObserver(); if (debugMode) { console.log('Original row:', originalRow); } } }); } }); }); function disconnectObserver() { observer.disconnect(); } function connectObserver() { observer.observe(targetNode, { childList: true }); } connectObserver(); } //clone function and handle change id, name, class, and select2 intite issues function cloneAndModifyElement(originalElement, debugMode = false) { // Check if the element or any of its similar siblings have already been cloned if (originalElement.hasClass('is-cloned')) { if (debugMode) { console.log('Element already cloned.'); } return; // Exit the function if already cloned } var clonedElement = originalElement.clone(); // Mark the original and cloned elements originalElement.addClass('is-cloned'); clonedElement.addClass('cloned-element'); // Update all IDs, names, and classes within the cloned row clonedElement.find('[id], [name], [for], [class]').each(function() { var elem = $(this); // Prepend 'm_' to IDs, unless the ID starts with 'select2' var elemId = elem.attr('id'); if (elemId) { // Check if elemId is not undefined or null if (!elemId.startsWith('select2')) { elem.attr('id', 'm_' + elemId); } } // Prepend 'm_' to names if (elem.attr('name')) { elem.attr('name', 'm_' + elem.attr('name')); } // Prepend 'm_' to the 'for' attribute in labels if any if (elem.attr('for')) { elem.attr('for', 'm_' + elem.attr('for')); } // Modify specific classes starting with 'row_' if (elem.attr('class')) { var classes = elem.attr('class').split(/\s+/); var modifiedClasses = classes.map(cls => { // Check if the class starts with 'row_' if (cls.startsWith("row_")) { return 'm_' + cls; // Prepend 'm_' to classes starting with 'row_' } // Check if the class matches common Bootstrap or framework patterns if (/^(fa|fas|far|fab|btn|col|container|d-|justify|align|text|border|bg-|table|nav|modal|dropdown|input|form|alert|pagination|badge|form-control|input-sm|input-lg|visible|hidden|active|disabled|carousel|glyph|accordion|panel|list-group|hide|select2|row|pull-right|paid_on|number|lead|close).*/.test(cls)) { return cls; // Return the class unchanged } // For all other classes, prepend 'm_' as well return 'm_' + cls; }).join(' '); elem.attr('class', modifiedClasses); } // Modify data-target attribute to start with #m_ if (elem.attr('data-target')) { var targetValue = elem.attr('data-target'); elem.attr('data-target', targetValue.replace(/^#/, '#m_')); } }); // Modify id of the main element passed start with m_ clonedElement.attr('id', 'm_' + clonedElement.attr('id')); // Insert the modified clone after the original and optionally hide the original row originalElement.after(clonedElement); if (debugMode) { console.log('Cloned row:', clonedElement); console.log('Cloned row appended after the original row.'); } // Capture styles from original Select2 and reinitialize Select2 with those styles on the clone originalElement.find('.select2-hidden-accessible').each(function() { var originalSelect = $(this); var originalSelect2Container = originalSelect.data('select2').$container; var originalWidth = originalSelect2Container.width(); var originalStyles = { width: originalWidth, height: originalSelect2Container.css('height'), lineHeight: originalSelect2Container.css('line-height'), borderColor: originalSelect2Container.find('.select2-selection').css('border-color') }; clonedElement.find('.select2-hidden-accessible').each(function() { var $select = $(this); // Safely destroy Select2 only if it's initialized if ($select.data('select2')) { $select.select2('destroy'); } else { if (debugMode) { console.log('Select2 not initialized on this element, skipping destroy.'); } } }).end().find('.select2-container').remove(); // Reinitialize Select2 on cloned element var newSelect = clonedElement.find('#' + 'm_' + originalSelect.attr('id')); newSelect.select2(); // Initialize Select2 // Apply captured styles to the new Select2 container var newSelect2Container = newSelect.data('select2').$container; newSelect2Container.css({ 'width': originalStyles.width + 'px', // Ensure width is the same 'height': originalStyles.height, 'line-height': originalStyles.lineHeight }); newSelect2Container.find('.select2-selection').css({ 'border-color': originalStyles.borderColor }); }); // Reinitialize Select2 for select elements that had the plugin clonedElement.find('.select2-hidden-accessible').on('change', function() { if (debugMode) { console.log('Select2 value changed on cloned element!'); } // Additional logic for handling changes can be implemented here }); handleRow(clonedElement) originalElement.hide(); // Uncomment if you want to hide the original row } //handle multi currency change rate function handleMultiCurrencyChange(){ $('#m_currency_id').on('change', function(){ var currencyId = $(this).val(); var rate = 1; $.ajax({ url: '/exchange-rate/' + currencyId, method: 'POST', data: {id: currencyId}, success: function(response){ if (response.success) { if(response.data.type === 'api'){ // Call fetchApiCurrencyRate with a callback function to handle the response fetchApiCurrencyRate(currencyId).done(function(apiResponse) { if (apiResponse.success) { rate = apiResponse.rate; console.log("Return API rate:", rate); updateCurrencyFields(response.data, rate); updateAllRows(); } }); }else{ rate = response.data.exchange_rate; updateCurrencyFields(response.data, rate); updateAllRows(); } } else { // Handle the scenario when success is false console.error("Failed to fetch data: " + response.message); } }, error: function(xhr, status, error){ // Handle errors if any console.error("Error: " + xhr.responseText); } }); }); } // Function to fetch currency rate from API function fetchApiCurrencyRate(currencyId) { // Create a deferred object var deferred = $.Deferred(); $.ajax({ url: '/api-currency-rate/' + currencyId, method: 'GET', success: function(apiResponse) { if (apiResponse.success) { var rate = apiResponse.rates[apiResponse.code]; if (rate !== undefined) { // Resolve the deferred object with success and the rate value deferred.resolve({ success: true, rate: rate }); } else { console.error("API currency rate fetch failed: Rate for the code is undefined"); // Reject the deferred object with an error message deferred.reject("Rate for the code is undefined"); } } else { console.error("API currency rate fetch failed."); // Reject the deferred object with an error message deferred.reject("API currency rate fetch failed"); } }, error: function(xhr, status, error) { console.error("Error fetching API currency rate: " + xhr.responseText); // Reject the deferred object with an error message deferred.reject("Error fetching API currency rate"); } }); // Return the promise associated with the deferred object return deferred.promise(); } function updateCurrencyFields(response, rate) { $('#m_exchange_rate').val(rate); $('#m_code').val(response.code); $('#m_symbol').val(response.symbol); $('#m_thousand').val(response.thousand_separator); $('#m_decimal').val(response.decimal_separator); } //update all table calculation function updateAllRows() { // Check if elements with class .m_purchase_quantity exist if ($('.m_purchase_quantity').length > 0) { $('.m_purchase_quantity').each(function() { row = $(this).closest('tr'); handlePurchaseRow(row); }); } // Check if elements with class .m_pos_quantity exist else if ($('.m_pos_quantity').length > 0) { $('.m_pos_quantity').each(function() { row = $(this).closest('tr'); handlePosRow(row); }); } else { // Neither class is found console.log("No elements with class .m_pos_quantity or .m_purchase_quantity exist on this page."); } } function handleRow(row){ // Check if elements with class .m_purchase_quantity exist if ($('.m_purchase_quantity').length > 0) { handlePurchaseRow(row); } // Check if elements with class .m_pos_quantity exist else if ($('.m_pos_quantity').length > 0) { handlePosRow(row); } else { // Neither class is found console.log("No elements with class .m_pos_quantity or .m_purchase_quantity exist on this page."); } } function multiCurrencyTransFromEn( input, show_symbol = true, use_page_currency = false, precision = __currency_precision, is_quantity = false ) { var m_s = $('#m_symbol').val(); if ($('#m_symbol').val()) { console.log("M symbol presented"); var s = m_s; var thousand = $('#m_thousand').val(); var decimal = $('#m_decimal').val(); } else { console.log("Default symbol presented"); var s = __currency_symbol; var thousand = __currency_thousand_separator; var decimal = __currency_decimal_separator; } symbol = ''; var format = '%s%v'; if (show_symbol) { symbol = s; format = '%s %v'; if (__currency_symbol_placement == 'after') { format = '%v %s'; } } if (is_quantity) { precision = __quantity_precision; } return accounting.formatMoney(input, symbol, precision, thousand, decimal, format); } $(document).on('click', '#m_toggle_additional_expense', function() { $('#m_additional_expenses_div').toggle(); }); //observe Multi currency change handleMultiCurrencyChange();