PageRenderTime 75ms CodeModel.GetById 37ms RepoModel.GetById 0ms app.codeStats 0ms

/public/javascripts/afford_content.js

https://bitbucket.org/keithelliott/simplecalc_web
JavaScript | 660 lines | 551 code | 105 blank | 4 comment | 2 complexity | 626cf636d1ffcad5f78a66c85f0db32f MD5 | raw file
  1. define([
  2. 'jquery',
  3. 'underscore',
  4. 'backbone',
  5. 'backbone.marionette',
  6. 'accounting',
  7. 'afford',
  8. 'text!templates/afford_income_input.html',
  9. 'text!templates/afford_expense.html',
  10. 'text!templates/afford_ratios.html',
  11. 'text!templates/afford_loan.html'
  12. ], function($,_,Backbone, Marionette, accounting, AffordCalc, income_input, afford_expense, afford_ratio, loan_tmpl){
  13. var AffordLayout = {};
  14. var ModalRegion = Backbone.Marionette.Region.extend({
  15. el: "#modal",
  16. constructor: function(){
  17. _.bindAll(this);
  18. Backbone.Marionette.Region.prototype.constructor.apply(this, arguments);
  19. this.on("view:show", this.showModal, this);
  20. },
  21. getEl: function(selector){
  22. var $el = $(selector);
  23. $el.on("hidden", this.close);
  24. return $el;
  25. },
  26. showModal: function(view){
  27. view.on("close", this.hideModal, this);
  28. this.$el.modal('show');
  29. },
  30. hideModal: function(){
  31. this.$el.modal('hide');
  32. }
  33. });
  34. var Afford_Layout = Backbone.Marionette.Layout.extend({
  35. template: '#afford_template',
  36. regions: {
  37. 'sidebar' : '#demo_sidebar',
  38. 'income_input' : '#afford_income_input',
  39. 'income_table' : '#income_table',
  40. 'income_subtotal' : '#income_total',
  41. 'expense_input' : '#afford_expense',
  42. 'expense_table' : '#expense_table',
  43. 'expense_subtotal' : '#expense_total',
  44. 'debt_view' : '#afford_credit',
  45. 'loan_view' : '#afford_loan',
  46. 'outputs' : '#afford_outputs'
  47. },
  48. display: function(){
  49. var sidebar = new AffordLayout.Sidebar();
  50. var income_list = new AffordLayout.IncomeList();
  51. var income_input = new AffordLayout.income_input({collection: income_list});
  52. var income_table = new AffordLayout.income_table({collection: income_list});
  53. var income_subtotal = new AffordLayout.income_subtotal({collection:income_list});
  54. var expense_list = new AffordLayout.ExpenseList();
  55. var expense_input = new AffordLayout.expense_input({collection:expense_list});
  56. var expense_table = new AffordLayout.expense_table({collection: expense_list});
  57. var expense_subtotal = new AffordLayout.expense_subtotal({collection:expense_list});
  58. var debt = new AffordLayout.DebtRatios();
  59. var debtView = new AffordLayout.debt_view({model:debt});
  60. var loan = new AffordLayout.LoanAssumptions();
  61. var loan_view = new AffordLayout.LoanView({model:loan});
  62. var outputs = new AffordLayout.OutputsModel();
  63. var outputs_view = new AffordLayout.OutputsView({income_list: income_list, expense_list: expense_list,
  64. debt: debt, loan: loan, model:outputs});
  65. var display_outputs = function(){
  66. console.log('clearing income list');
  67. var affordCalc = new AffordCalc();
  68. var totalMonthlyIncome = 0;
  69. income_list.models.forEach(function(income){
  70. console.log('income:' + income.get('amount'));
  71. var moAmount = affordCalc.convertMonthly(income.get('amount'));
  72. console.log('adding ' + income.get('name') + ': ' + moAmount + ' to list');
  73. totalMonthlyIncome = totalMonthlyIncome + moAmount;
  74. });
  75. var totalMonthlyDebt = 0;
  76. expense_list.models.forEach(function(expense){
  77. var moAmount = expense.get('amount');
  78. console.log('adding ' + expense.get('name') + ': ' + moAmount + ' to list');
  79. totalMonthlyDebt = totalMonthlyDebt + accounting.unformat(moAmount);
  80. });
  81. var monthlyPaymentAggressive = affordCalc.calculateMonthlyPayment({
  82. totalMonthlyIncome : totalMonthlyIncome,
  83. totalMonthlyDebt : totalMonthlyDebt,
  84. debtToIncomeRatio : debt.get('DebtToIncomeAggressive') / 100,
  85. propertyTaxes : accounting.unformat(loan.get('tax_rate')),
  86. homeOwnersFeesYrly : accounting.unformat(loan.get('homeowners_amount'))
  87. });
  88. var monthlyPaymentConservative = affordCalc.calculateMonthlyPayment({
  89. totalMonthlyIncome : totalMonthlyIncome,
  90. totalMonthlyDebt : totalMonthlyDebt,
  91. debtToIncomeRatio : debt.get('DebtToIncomeConservative') / 100,
  92. propertyTaxes : accounting.unformat(loan.get('tax_rate')),
  93. homeOwnersFeesYrly : accounting.unformat(loan.get('homeowners_amount'))
  94. });
  95. var maxLoanAmount = affordCalc.calculateLoanAmount({
  96. payment: monthlyPaymentAggressive,
  97. interestRate: loan.get('interest_rate')/100,
  98. term: accounting.unformat(loan.get('term'))
  99. });
  100. var minLoanAmount = affordCalc.calculateLoanAmount({
  101. payment: monthlyPaymentConservative,
  102. interestRate: loan.get('interest_rate')/100,
  103. term: accounting.unformat(loan.get('term'))
  104. });
  105. outputs.set({'loan_aggressive' : accounting.formatMoney(maxLoanAmount),
  106. 'loan_conservative': accounting.formatMoney(minLoanAmount),
  107. 'payment_aggressive': accounting.formatMoney(monthlyPaymentAggressive),
  108. 'payment_conservative': accounting.formatMoney(monthlyPaymentConservative),
  109. 'taxes': accounting.formatMoney(loan.get('tax_rate') / 12),
  110. 'total_aggressive': accounting.formatMoney(monthlyPaymentAggressive + accounting.unformat(loan.get('tax_rate'))/12),
  111. 'total_conservative' : accounting.formatMoney(monthlyPaymentConservative + accounting.unformat(loan.get('tax_rate'))/12)});
  112. };
  113. income_list.on('change', display_outputs);
  114. income_list.on('add', display_outputs);
  115. income_list.on('remove', display_outputs);
  116. expense_list.on('change',display_outputs);
  117. expense_list.on('add',display_outputs);
  118. expense_list.on('remove',display_outputs);
  119. debt.on('change',display_outputs);
  120. loan.on('change', display_outputs);
  121. income_input.on('income:focus', function(){
  122. sidebar.focus_income();
  123. });
  124. debtView.on('ratio:focus', function(expense){
  125. sidebar.focus_ratio();
  126. });
  127. loan_view.on('loan:focus', function(expense){
  128. sidebar.focus_loan();
  129. });
  130. expense_input.on('expense:focus', function(expense){
  131. sidebar.focus_expense();
  132. });
  133. this.sidebar.show(sidebar);
  134. this.income_input.show(income_input);
  135. this.income_table.show(income_table);
  136. this.income_subtotal.show(income_subtotal);
  137. this.expense_input.show(expense_input);
  138. this.expense_table.show(expense_table);
  139. this.expense_subtotal.show(expense_subtotal);
  140. this.debt_view.show(debtView);
  141. this.loan_view.show(loan_view);
  142. this.outputs.show(outputs_view);
  143. }
  144. });
  145. AffordLayout.Afford = Afford_Layout;
  146. var sidebar = Backbone.Marionette.ItemView.extend({
  147. template: '#affordMenu',
  148. events: {
  149. 'click #income_menu a' : 'focus_income',
  150. 'click #expense_menu a': 'focus_expense',
  151. 'click #ratio_menu a': 'focus_ratio',
  152. 'click #loan_menu a': 'focus_loan',
  153. 'click #outputs_menu a': 'focus_outputs'
  154. },
  155. ui:{
  156. income_menu : '#income_menu',
  157. expense_menu : '#expense_menu',
  158. ratio_menu : '#ratio_menu',
  159. loan_menu : '#loan_menu',
  160. outputs_menu : '#outputs_menu'
  161. },
  162. focus_income: function(e){
  163. this.ui.income_menu.siblings().removeClass('active');
  164. this.ui.income_menu.addClass('active');
  165. return false;
  166. },
  167. focus_expense: function(e){
  168. this.ui.expense_menu.siblings().removeClass('active');
  169. this.ui.expense_menu.addClass('active');
  170. return false;
  171. },
  172. focus_ratio: function(e){
  173. this.ui.ratio_menu.siblings().removeClass('active');
  174. this.ui.ratio_menu.addClass('active');
  175. return false;
  176. },
  177. focus_loan: function(e){
  178. this.ui.loan_menu.siblings().removeClass('active');
  179. this.ui.loan_menu.addClass('active');
  180. return false;
  181. },
  182. focus_outputs: function(e){
  183. this.ui.outputs_menu.siblings().removeClass('active');
  184. this.ui.outputs_menu.addClass('active');
  185. return false;
  186. }
  187. });
  188. var income_input = Backbone.Marionette.ItemView.extend({
  189. template: income_input,
  190. events:{
  191. 'click #income_submit' : 'addRow',
  192. 'change #income_name' : 'nameAdded',
  193. 'change #income_amount' : 'amountAdded',
  194. 'change #income_frequency': 'freq_changed'
  195. },
  196. triggers: {
  197. 'mouseover #income_div': 'income:focus'
  198. },
  199. ui: {
  200. income_amount: '#income_amount',
  201. income_frequency: 'select#income_frequency',
  202. income_name : '#income_name',
  203. income_submit : '#income_submit'
  204. },
  205. freq_changed: function(e){
  206. this.ui.income_amount.attr('placeholder',this.ui.income_frequency.val() + ' Amount');
  207. },
  208. nameAdded: function(e){
  209. console.log(this.ui.income_name.val() + ' added');
  210. },
  211. amountAdded: function(e){
  212. console.log(this.ui.income_amount.val() + ' added');
  213. },
  214. addRow: function(e){
  215. e.preventDefault();
  216. console.log('new income added');
  217. var amount = parseFloat(this.ui.income_amount.val());
  218. if (this.ui.income_frequency.val() === 'Monthly'){
  219. amount = amount * 12;
  220. }
  221. var income = new Income({'name': this.ui.income_name.val(),
  222. 'amount': accounting.formatMoney(amount)});
  223. this.ui.income_name.attr('value','');
  224. this.ui.income_amount.attr('value', '');
  225. this.collection.add(income);
  226. }
  227. });
  228. var Income = Backbone.Model.extend();
  229. var IncomeList = Backbone.Collection.extend({
  230. model: Income
  231. });
  232. var IncomeView = Backbone.Marionette.ItemView.extend({
  233. template: '#income_item_view',
  234. tagName: 'tr',
  235. events: {
  236. 'click tr:hover button.income_edit' : 'edit',
  237. 'click tr:hover button.income_edit_done': 'done'
  238. },
  239. ui:{
  240. remove_btn: 'tr:hover button.income_remove',
  241. edit_btn: 'tr:hover button.income_edit',
  242. done_btn: 'tr:hover button.income_edit_done',
  243. text_input: 'tr:hover span.txt_input',
  244. text_cell: 'tr:hover span.txt_cell',
  245. name_cell: 'tr:hover .name_cell',
  246. amount_cell: 'tr:hover .amount_cell'
  247. },
  248. triggers:{
  249. 'click tr:hover button.income_remove' : 'remove:model'
  250. },
  251. edit: function(e){
  252. e.preventDefault();
  253. console.log('edit called');
  254. this.ui.text_input.removeClass('hide');
  255. this.ui.text_cell.addClass('hide');
  256. this.ui.edit_btn.show();
  257. this.ui.done_btn.hide();
  258. return false;
  259. },
  260. done: function(e){
  261. e.preventDefault();
  262. console.log('done called');
  263. this.ui.text_input.addClass('hide');
  264. this.ui.text_cell.removeClass('hide');
  265. this.ui.edit_btn.hide();
  266. this.ui.done_btn.show();
  267. this.update(e);
  268. return false;
  269. },
  270. update: function(e){
  271. e.preventDefault();
  272. console.log('update called');
  273. this.model.set({'name': this.ui.name_cell.val(),
  274. 'amount': accounting.formatMoney(this.ui.amount_cell.val()) });
  275. this.render();
  276. }
  277. });
  278. var IncomeCollectionView = Backbone.Marionette.CompositeView.extend({
  279. template: '#income_list_view',
  280. id:'incomelist',
  281. tagName: 'table',
  282. className: 'table table-striped table-condensed table-hover table-bordered',
  283. itemView: IncomeView,
  284. initialize: function(){
  285. this.bindTo(this, 'itemview:remove:model', this.modelDeleted);
  286. },
  287. modelDeleted: function(view){
  288. console.log('removed view');
  289. this.collection.remove(view.model);
  290. this.display();
  291. },
  292. display: function(model){
  293. console.log('displaying income');
  294. this.render();
  295. },
  296. appendHtml: function(collectionView, itemView){
  297. console.log('collection has ' + collectionView.collection.length + ' items');
  298. collectionView.$("tbody").append(itemView.el);
  299. }
  300. });
  301. var income_subtotal = Backbone.Marionette.ItemView.extend({
  302. template: '#income_total',
  303. initialize: function(){
  304. this.bindTo(this.collection, "add", this.collectionChanged);
  305. this.bindTo(this.collection, "remove", this.collectionChanged);
  306. this.bindTo(this.collection, "change", this.collectionChanged);
  307. },
  308. collectionChanged: function(model){
  309. console.log('collection changed');
  310. var total = 0;
  311. this.collection.models.forEach(function(val){
  312. total = total + accounting.unformat(val.get('amount'));
  313. });
  314. console.log('total: ' + total);
  315. $(this.el).html(accounting.formatMoney(total));
  316. }
  317. });
  318. //expenses
  319. var expense_input = Backbone.Marionette.ItemView.extend({
  320. template: afford_expense,
  321. events:{
  322. 'click #expense_submit' : 'addRow',
  323. 'change #expense_name' : 'nameAdded',
  324. 'change #expense_amount' : 'amountAdded',
  325. 'change #expense_frequency': 'freq_changed'
  326. },
  327. ui:{
  328. expense_amount: '#expense_amount',
  329. expense_frequency: 'select#expense_frequency',
  330. expense_name: '#expense_name',
  331. expense_amount: '#expense_amount'
  332. },
  333. triggers:{
  334. 'mouseover #expense_div' : 'expense:focus'
  335. },
  336. freq_changed: function(e){
  337. this.ui.expense_amount.attr('placeholder',this.ui.expense_frequency.val() + ' Amount');
  338. },
  339. nameAdded: function(e){
  340. console.log(this.ui.expense_name.val() + ' added');
  341. },
  342. amountAdded: function(e){
  343. console.log(this.ui.expense_amount.val() + ' added');
  344. },
  345. addRow: function(e){
  346. e.preventDefault();
  347. console.log('new expense added');
  348. var amount = parseFloat(this.ui.expense_amount.val());
  349. if (this.ui.expense_frequency.val() === 'Yearly'){
  350. amount = amount / 12;
  351. }
  352. var expense = new Expense({'name': this.ui.expense_name.val(),
  353. 'amount': accounting.formatMoney(amount)});
  354. this.ui.expense_name.attr('value','');
  355. this.ui.expense_amount.attr('value', '');
  356. this.collection.add(expense);
  357. }
  358. });
  359. var Expense = Backbone.Model.extend();
  360. var ExpenseList = Backbone.Collection.extend({
  361. model: Expense
  362. });
  363. var ExpenseView = Backbone.Marionette.ItemView.extend({
  364. template: '#expense_item_view',
  365. tagName: 'tr',
  366. events: {
  367. 'click tr:hover button.expense_edit' : 'edit',
  368. 'click tr:hover button.expense_edit_done': 'done'
  369. },
  370. ui: {
  371. remove_btn: 'tr:hover button.expense_remove',
  372. edit_btn: 'tr:hover button.expense_edit',
  373. done_btn: 'tr:hover button.expense_edit_done',
  374. text_input: 'tr:hover span.txt_input',
  375. text_cell: 'tr:hover span.txt_cell',
  376. name_cell: 'tr:hover .name_cell',
  377. amount_cell: 'tr:hover .amount_cell'
  378. },
  379. triggers:{
  380. 'click tr:hover button.expense_remove' : 'remove:model'
  381. },
  382. edit: function(e){
  383. e.preventDefault();
  384. console.log('edit called');
  385. this.ui.text_input.removeClass('hide');
  386. this.ui.text_cell.addClass('hide');
  387. this.ui.done_btn.show();
  388. this.ui.edit_btn.hide();
  389. return false;
  390. },
  391. done: function(e){
  392. e.preventDefault();
  393. console.log('edit called');
  394. this.ui.text_input.addClass('hide');
  395. this.ui.text_cell.removeClass('hide');
  396. this.ui.done_btn.hide();
  397. this.ui.edit_btn.show();
  398. },
  399. update: function(e){
  400. e.preventDefault();
  401. console.log('update called');
  402. this.model.set({'name': this.ui.name_cell.val(),
  403. 'amount': accounting.formatMoney(this.ui.amount_cell.val()) });
  404. this.render();
  405. }
  406. });
  407. var ExpenseCollectionView = Backbone.Marionette.CompositeView.extend({
  408. template: '#expense_list_view',
  409. id:'expenselist',
  410. tagName: 'table',
  411. className: 'table table-striped table-condensed table-hover table-bordered',
  412. itemView: ExpenseView,
  413. initialize: function(){
  414. this.bindTo(this, 'itemview:remove:model', this.modelDeleted);
  415. },
  416. modelDeleted: function(view){
  417. console.log('removed view');
  418. this.collection.remove(view.model);
  419. this.display();
  420. },
  421. display: function(model){
  422. console.log('displaying income');
  423. this.render();
  424. },
  425. appendHtml: function(collectionView, itemView){
  426. collectionView.$("tbody").append(itemView.el);
  427. }
  428. });
  429. var expense_subtotal = Backbone.Marionette.ItemView.extend({
  430. template: '#expense_total',
  431. initialize: function(){
  432. this.bindTo(this.collection, "add", this.collectionChanged);
  433. this.bindTo(this.collection, "remove", this.collectionChanged);
  434. this.bindTo(this.collection, "change", this.collectionChanged);
  435. },
  436. collectionChanged: function(model){
  437. console.log('collection changed');
  438. var total = 0;
  439. this.collection.models.forEach(function(val){
  440. total = total + accounting.unformat(val.get('amount'));
  441. });
  442. console.log('total: ' + total);
  443. $(this.el).html(accounting.formatMoney(total));
  444. }
  445. });
  446. // ratios
  447. var DebtRatios = Backbone.Model.extend();
  448. var DebtView = Backbone.Marionette.ItemView.extend({
  449. template: afford_ratio,
  450. events:{
  451. 'change #d-to-i-conservative' : 'updateDtoIConservative',
  452. 'change #d-to-i-aggressive' : 'updateDtoIAggressive'
  453. },
  454. triggers:{
  455. 'mouseover #ratio_div' : 'ratio:focus'
  456. },
  457. ui:{
  458. d_to_i_conservative: '#d-to-i-conservative',
  459. d_to_i_aggressive: '#d-to-i-aggressive'
  460. },
  461. updateDtoIConservative: function(e){
  462. this.model.set({'DebtToIncomeConservative' : this.ui.d_to_i_conservative.val()});
  463. console.log('conservative ratio updated to: ' + this.model.get('DebtToIncomeConservative'));
  464. },
  465. updateDtoIAggressive: function(e){
  466. this.model.set({'DebtToIncomeAggressive' : this.ui.d_to_i_aggressive.val()});
  467. console.log('aggressive ratio updated to:' + this.model.get('DebtToIncomeAggressive'));
  468. }
  469. });
  470. // loan assumptions
  471. var LoanAssumptions = Backbone.Model.extend();
  472. var LoanView = Backbone.Marionette.ItemView.extend({
  473. template: loan_tmpl,
  474. events:{
  475. 'change input#interest_rate': 'update_rate',
  476. 'change input#tax_rate': 'update_tax',
  477. 'change input#homeowners_amount' : 'update_homeowners',
  478. 'change input#loan_term' : 'update_term'
  479. },
  480. triggers:{
  481. 'mouseover #loan_div': 'loan:focus'
  482. },
  483. ui: {
  484. interest_rate: '#interest_rate',
  485. loan_term: '#loan_term',
  486. tax_rate: '#tax_rate',
  487. homeowners: '#homeowners_amount'
  488. },
  489. update_rate: function(){
  490. this.model.set({'interest_rate': this.ui.interest_rate.val()});
  491. console.log('interest rate updated');
  492. },
  493. update_term: function(){
  494. this.model.set({'term': this.ui.loan_term.val()});
  495. console.log('term updated');
  496. },
  497. update_tax: function(){
  498. this.model.set({'tax_rate': this.ui.tax_rate.val()});
  499. console.log('tax rate updated');
  500. },
  501. update_homeowners : function(){
  502. this.model.set({'homeowners_amount': this.ui.homeowners.val()});
  503. console.log('homeowners updated');
  504. }
  505. });
  506. //Outputs
  507. var OutputsModel = Backbone.Model.extend();
  508. var OutputsView = Backbone.Marionette.ItemView.extend({
  509. template: '#afford_output_tmpl',
  510. initialize: function(options){
  511. this.bindTo(this.model, 'change', this.display);
  512. },
  513. ui:{
  514. loan_aggressive: '#afford_aggressive_loan',
  515. loan_conservative: '#afford_conservative_loan',
  516. aggressive_pmt: 'td#afford_aggressive_pmt',
  517. conservative_pmt: '#afford_conservative_pmt',
  518. aggressive_taxes: '#afford_aggressive_taxes',
  519. conservative_taxes: '#afford_conservative_taxes',
  520. aggressive_total: '#afford_aggressive_total',
  521. conservative_total: '#afford_conservative_total'
  522. },
  523. display: function(){
  524. console.log('outputs displayed');
  525. this.ui.loan_aggressive.html(this.model.get('loan_aggressive'));
  526. this.ui.loan_conservative.html(this.model.get('loan_conservative'));
  527. this.ui.aggressive_pmt.html(this.model.get('payment_aggressive'));
  528. this.ui.conservative_pmt.html(this.model.get('payment_conservative'));
  529. this.ui.aggressive_taxes.html(this.model.get('taxes'));
  530. this.ui.conservative_taxes.html(this.model.get('taxes'));
  531. this.ui.aggressive_total.html(this.model.get('total_aggressive'));
  532. this.ui.conservative_total.html(this.model.get('total_conservative'));
  533. }
  534. });
  535. AffordLayout.Sidebar = sidebar;
  536. AffordLayout.income_input = income_input;
  537. AffordLayout.Income = Income;
  538. AffordLayout.IncomeList = IncomeList;
  539. AffordLayout.IncomeView = IncomeView;
  540. AffordLayout.income_table = IncomeCollectionView;
  541. AffordLayout.income_subtotal = income_subtotal;
  542. AffordLayout.expense_input = expense_input;
  543. AffordLayout.Expense = Expense;
  544. AffordLayout.ExpenseList = ExpenseList;
  545. AffordLayout.ExpenseView = ExpenseView;
  546. AffordLayout.expense_table = ExpenseCollectionView;
  547. AffordLayout.expense_subtotal = expense_subtotal;
  548. AffordLayout.DebtRatios = DebtRatios;
  549. AffordLayout.debt_view = DebtView;
  550. AffordLayout.LoanAssumptions = LoanAssumptions;
  551. AffordLayout.LoanView = LoanView;
  552. AffordLayout.OutputsModel = OutputsModel;
  553. AffordLayout.OutputsView = OutputsView;
  554. return AffordLayout;
  555. });