знатоки JavaScript!
Я разрабатываю свой ипотечный калькулятор, и, похоже, мне очень нужна ваша помощь.
В моем приложении реализован обычный ипотечный калькулятор, в котором вы вводите сумму ипотечного кредита, срок и процентную ставку, и предполагается, что оно должно предоставлять разбивку платежей по кредиту по месяцам с использованием плагина Jquery datatables.
Пока мой html:
$(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, ''));
}
});
$('#breakdown').on('click', () => {
//get the array of payments
var amount = $('#amount').val();
var months = $('#term').val();
var interest = $('#interest').val();
var breakdown = [];
for(var i = 0; i < months; i++){
var row = {};
var monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
row.month = monthNames[i%12]+', '+$('#year').val();
row.principal = (amount/months).toFixed();
row.interest = ((amount*interest)/months).toFixed();
row.balance = (amount*(1-i/months)).toFixed();
breakdown.push(row);
}
$('#mortgageTable').DataTable({
data: breakdown,
destroy: true,
dom: 'ftip',
columnDefs: [
{targets: 0, data: 'month', title: 'Month'},
{targets: 1, data: 'principal', title: 'Principal'},
{targets: 2, data: 'interest', title: 'Interest'},
{targets: 3, data: 'balance', title: 'Balance'},
]
});
});
});
<!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">
<option value = "Jan">Jan</option>
<option value = "Jan">Feb</option>
<option value = "Jan">Mar</option>
<option value = "Jan">Apr</option>
<option value = "Jan">May</option>
<option value = "Jan">Jun</option>
<option value = "Jan">Jul</option>
<option value = "Jan">Aug</option>
<option value = "Jan">Sep</option>
<option value = "Jan">Oct</option>
<option value = "Jan">Nov</option>
<option value = "Jan">Dec</option>
</select>
<select id = "year">
<option value = "2019">2019</option>
<option value = "2020">2020</option>
</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>
И у меня это работает в некотором роде, но главная проблема, с которой я сталкиваюсь, заключается в том, что платежи за каждый последующий месяц должны уменьшаться из-за уменьшения остатка долга и, как такового, уменьшения процентных платежей.
Есть ли способ справиться с этим, используя функции datatables?
Итак, мой ожидаемый результат за 100 000 долларов, 36 месяцев, 5% должен быть таким:
principal interest balance
2777.78 416.67 100000.00
2777.78 405.09 97222.22
2777.78 393.52 94444.44
2777.78 381.94 91666.67
2777.78 370.37 88888.89
В моей текущей реализации только первая строка рассчитывается правильно, а в остальных строках нет «баланса», а «проценты» уменьшаются пропорционально.
На самом деле, ваши вычисления не нуждаются в «рекурсии». Для достижения желаемого результата вы можете использовать опцию 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))},
...
]
});
render
, вы можете сэкономить еще немного производительности, отображая свою таблицу на лету (без предварительной подготовки объекта данных)Кроме того, вы можете принять во внимание несколько предложений, которые могут сделать ваш код немного более эффективным:
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>
также могут заполняться параметрами динамически, так что вам не нужно обновлять свой 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>