Внедрить рекурсивные расчеты для моего ипотечного калькулятора - PullRequest
2 голосов
/ 21 мая 2019

экспертов по 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

В моей текущей реализации правильно рассчитана только первая строка, а в оставшейся строке пропорционально не уменьшены значения «баланс» и «процент»

1 Ответ

5 голосов
/ 24 мая 2019

На самом деле ваши вычисления не нуждаются в какой-либо «рекурсии».Для достижения желаемого результата вы можете использовать опцию 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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...