На самом деле ваши вычисления не нуждаются в какой-либо «рекурсии».Для достижения желаемого результата вы можете использовать опцию columns.render
, чтобы рассчитать процентные платежи на основе текущего остатка и месяца.
Таким образом, решение сводится к следующей строке:
render: (data, type, row, meta) => ($('#amount').val()*(1-meta.row/$('#term').val())*$('#interest').val()/1200).toFixed(2)}
Однако в вашем коде масса других проблем:
- Ваша строка даты не будет оценена должным образом, начиная со второго года, поэтому вам лучше сделать что-нибудь, например:
const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const datatable = $('#mortgageTable').DataTable({
...
columns: [
{data: 'month', title: 'Month', render: (data, type, row, meta) => monthNames[meta.row%12]+', '+parseFloat(parseFloat($('#year').val())+Math.floor(meta.row/12))},
...
]
});
- , вам на самом деле не нужно уничтожать и создавать свой DataTableпри изменении входных данных вы можете просто очистить его содержимое и заполнить его соответствующими данными, и это будет намного быстрее с точки зрения производительности
- , учитывая, что вы можете достичь своей цели с помощью опций
render
, вы можете сохранить некоторыеповышенная производительность рендеринга таблицы на лету (без предварительной подготовки объекта данных)
Кроме того, вы можете принять во внимание несколько предложений, которые могут сделать ваш код немного более эффективным:
- вы можете заполнить опции HTML месяцами одной строкой:
const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
$('#month').append(monthNames.reduce((options, month) => options += `<option value="${month}">${month}</option>`,''));
- ваши годы
<select>
могут быть заполнены параметрами динамическиy, так что вам не нужно обновлять HTML каждый год:
$('#year').append([...Array(2)].reduce((options, dummy, index) => options += `<option value="${(new Date()).getFullYear()+index}">${(new Date()).getFullYear()+index}</option>`,''))
- вы можете реализовать пользовательскую сортировку для данных первого столбца для лучшего взаимодействия с пользователем, например, если выЕсли для вашего первого столбца назначено
type: 'mmmyyyy'
, пользовательская сортировка может быть достигнута с помощью чего-то, например:
const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const dateVal = str => {
const dateParts = str.split(', ');
return parseFloat(12*dateParts[1])+monthNames.indexOf(dateParts[0]);
};
Object.assign($.fn.DataTable.ext.oSort, {
'mmmyyyy-asc': (a, b) => dateVal(a)-dateVal(b),
'mmmyyyy-desc': (a, b) => dateVal(b)-dateVal(a),
});
В конечном итоге полная демонстрационная версия вашего кода может выглядеть примерно так:
$(document).ready(() => {
//restrict input to certain types
$('[restrict]').on('keyup', function () {
switch ($(this).attr('restrict')) {
case 'integer':
$(this).val($(this).val().replace(/[^0-9]*/g, ''));
case 'float':
$(this).val($(this).val().replace(/[^\.0-9]*/g, ''));
}
});
//re-used month names array
const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
//turning 'MMM, YYYY' into value
const dateVal = str => {
const dateParts = str.split(', ');
return parseFloat(12*dateParts[1])+monthNames.indexOf(dateParts[0]);
};
//populate 'year', 'month' options dynamically
$('#month').append(monthNames.reduce((options, month) => options += `<option value="${month}">${month}</option>`,''));
$('#year').append([...Array(2)].reduce((options, dummy, index) => options += `<option value="${(new Date()).getFullYear()+index}">${(new Date()).getFullYear()+index}</option>`,''))
//breakdown frame
const breakdown = () => [...Array(parseFloat($('#term').val()) || 0)].map(item => ['month', 'principal', 'interest', 'balance'].reduce((res, header) => ({...res, [header]:''}), {}));
//feed datatable
const datatable = $('#mortgageTable').DataTable({
dom: 'ftip',
data: breakdown(),
columns: [
{data: 'month', type: 'mmmyyyy', title: 'Month', render: (data, type, row, meta) => monthNames[meta.row%12]+', '+parseFloat(parseFloat($('#year').val())+Math.floor(meta.row/12))},
{data: 'principal', title: 'Principal', render: () => ($('#amount').val()/$('#term').val()).toFixed(2)},
{data: 'interest', title: 'Interest', render: (data, type, row, meta) => ($('#amount').val()*(1-meta.row/$('#term').val())*$('#interest').val()/1200).toFixed(2)},
{data: 'balance', title: 'Balance', render: (data, type, row, meta) => ($('#amount').val()*(1-meta.row/$('#term').val())).toFixed(2)}
]
});
//datatable sorting by 'MMM, YYYY' value
Object.assign($.fn.DataTable.ext.oSort, {
'mmmyyyy-asc': (a, b) => dateVal(a)-dateVal(b),
'mmmyyyy-desc': (a, b) => dateVal(b)-dateVal(a),
});
//hide datatable initially
$('.dataTables_wrapper').hide();
//button click handler
$('#breakdown').on('click', () => {
datatable.clear().rows.add(breakdown()).draw();
$('.dataTables_wrapper').show();
});
});
<!doctype html>
<html>
<head>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
<script src="mortgagecalc.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css">
</head>
<body>
<div style="display:block; margin: 5px"><label>Loan amount, USD:</label><input id="amount" restrict="integer"></input></div>
<div style="display:block; margin: 5px"><label>Loan term, months:</label><input id="term" restrict="integer"></input></div>
<div style="display:block; margin: 5px"><label>First month:</label><select id="month"></select>
<select id="year"></select></div>
<div style="display:block; margin: 5px"><label>Interest rate, %:</label><input id="interest" restrict="float"></input></div>
<button id="breakdown">Mortgage breakdown</button>
<table id="mortgageTable"></table>
</body>
</html>