1jQuery(document).ready(function($) { 2 (function(factory) { 3 if (typeof define === 'function' && define.amd) { 4 // AMD. Register as an anonymous module. 5 define(['jquery'], factory); 6 } else { 7 // Browser globals 8 factory(jQuery); 9 } 10 } (function($) { 11 12 /** 13 * Range feature detection 14 * @return {Boolean} 15 */ 16 function supportsRange() { 17 var input = document.createElement('input'); 18 input.setAttribute('type', 'range'); 19 return input.type !== 'text'; 20 } 21 22 var pluginName = 'lrc', 23 pluginInstances = [], 24 inputrange = supportsRange(), 25 defaults = { 26 polyfill: true, 27 rangeClass: 'lrc', 28 disabledClass: 'lrc--disabled', 29 fillClass: 'lrc__fill', 30 handleClass: 'lrc__handle', 31 startEvent: ['mousedown', 'touchstart', 'pointerdown'], 32 moveEvent: ['mousemove', 'touchmove', 'pointermove'], 33 endEvent: ['mouseup', 'touchend', 'pointerup'] 34 }; 35 36 /** 37 * Delays a function for the given number of milliseconds, and then calls 38 * it with the arguments supplied. 39 * @param {Function} fn [description] 40 * @param {Number} wait [description] 41 * @return {Function} 42 */ 43 function delay(fn, wait) { 44 var args = Array.prototype.slice.call(arguments, 2); 45 return setTimeout(function(){ return fn.apply(null, args); }, wait); 46 } 47 48 /** 49 * Returns a debounced function that will make sure the given 50 * function is not triggered too much. 51 * @param {Function} fn Function to debounce. 52 * @param {Number} debounceDuration OPTIONAL. The amount of time in milliseconds for which we will debounce the function. (defaults to 100ms) 53 * @return {Function} 54 */ 55 function debounce(fn, debounceDuration) { 56 debounceDuration = debounceDuration || 100; 57 return function() { 58 if (!fn.debouncing) { 59 var args = Array.prototype.slice.apply(arguments); 60 fn.lastReturnVal = fn.apply(window, args); 61 fn.debouncing = true; 62 } 63 clearTimeout(fn.debounceTimeout); 64 fn.debounceTimeout = setTimeout(function(){ 65 fn.debouncing = false; 66 }, debounceDuration); 67 return fn.lastReturnVal; 68 }; 69 } 70 71 /** 72 * Plugin 73 * @param {String} element 74 * @param {Object} options 75 */ 76 function Plugin(element, options) { 77 this.$window = $(window); 78 this.$document = $(document); 79 this.$element = $(element); 80 this.options = $.extend( {}, defaults, options ); 81 this._defaults = defaults; 82 this._name = pluginName; 83 this.startEvent = this.options.startEvent.join('.' + pluginName + ' ') + '.' + pluginName; 84 this.moveEvent = this.options.moveEvent.join('.' + pluginName + ' ') + '.' + pluginName; 85 this.endEvent = this.options.endEvent.join('.' + pluginName + ' ') + '.' + pluginName; 86 this.polyfill = this.options.polyfill; 87 this.onInit = this.options.onInit; 88 this.onSlide = this.options.onSlide; 89 this.onSlideEnd = this.options.onSlideEnd; 90 91 // Plugin should only be used as a polyfill 92 if (this.polyfill) { 93 // Input range support? 94 if (inputrange) { return false; } 95 } 96 97 this.identifier = 'js-' + pluginName + '-' +(+new Date()); 98 this.min = parseFloat(this.$element[0].getAttribute('min') || 0); 99 this.max = parseFloat(this.$element[0].getAttribute('max') || 100); 100 this.value = parseFloat(this.$element[0].value || this.min + (this.max-this.min)/2); 101 this.step = parseFloat(this.$element[0].getAttribute('step') || 1); 102 this.noChange = false;
103 this.$fill = $('<div class="' + this.options.fillClass + '" />'); 104 this.$handle = $('<div class="' + this.options.handleClass + '" />'); 105 this.$range = $('<div class="' + this.options.rangeClass + '" id="' + this.identifier + '" />').insertAfter(this.$element).prepend(this.$fill, this.$handle); 106 this.lockvalue = -1; 107 108 // visually hide the input 109 this.$element.css({ 110 'position': 'absolute', 111 'width': '1px', 112 'height': '1px', 113 'overflow': 'hidden', 114 'opacity': '0' 115 }); 116 117 // Store context 118 this.handleDown = $.proxy(this.handleDown, this); 119 this.handleMove = $.proxy(this.handleMove, this); 120 this.handleEnd = $.proxy(this.handleEnd, this); 121 this.init(); 122 123 // Attach Events 124 var _this = this; 125 this.$window.on('resize' + '.' + pluginName, debounce(function() { 126 // Simulate resizeEnd event. 127 delay(function() { _this.update(); }, 300); 128 }, 20)); 129 130 this.$document.on(this.startEvent, '#' + this.identifier + ':not(.' + this.options.disabledClass + ')', this.handleDown); 131 132 // Listen to programmatic value changes 133 this.$element.on('change' + '.' + pluginName, function(e, data) { 134 if (data && data.origin === pluginName) { 135 return; 136 } 137 138 var value = e.target.value, 139 pos = _this.getPositionFromValue(value); 140 _this.setPosition(pos); 141 }); 142 } 143 144 Plugin.prototype.init = function() { 145 if (this.onInit && typeof this.onInit === 'function') { 146 this.onInit(); 147 } 148 this.update(); 149 }; 150 151 Plugin.prototype.lock = function(args) { 152 this.lockvalue = args[0]; 153 } 154 Plugin.prototype.unlock = function() { 155 this.lockvalue = -1; 156 } 157 Plugin.prototype.update = function() { 158 159 var hideagain = 0; 160 if (!this.$element.is(':visible')) { hideagain = 1; } 161 162 if (hideagain) this.$element.closest('.hidethis').show(); 163 this.handleWidth = this.$handle[0].offsetWidth; 164 this.rangeWidth = this.$range[0].offsetWidth; 165 this.maxHandleX = this.rangeWidth - this.handleWidth; 166 this.grabX = this.handleWidth / 2; 167 this.position = this.getPositionFromValue(this.value); 168 if (hideagain) this.$element.closest('.hidethis').hide(); 169 170 // Consider disabled state 171 if (this.$element[0].disabled) { 172 this.$range.addClass(this.options.disabledClass); 173 } else { 174 this.$range.removeClass(this.options.disabledClass); 175 } 176 177 this.setPosition(this.position); 178 179 }; 180 181 Plugin.prototype.handleDown = function(e) { 182 183 e.preventDefault(); 184 this.$document.on(this.moveEvent, this.handleMove); 185 this.$document.on(this.endEvent, this.handleEnd); 186 187 // If we click on the handle don't set the new position 188 if ((' ' + e.target.className + ' ').replace(/[\n\t]/g, ' ').indexOf(this.options.handleClass) > -1) { 189 return; 190 } 191 192 var posX = this.getRelativePosition(this.$range[0], e), 193 handleX = this.getPositionFromNode(this.$handle[0]) - this.getPositionFromNode(this.$range[0]); 194 195 this.setPosition(posX - this.grabX); 196 197 if (posX >
197= handleX && posX < handleX + this.handleWidth) { 198 this.grabX = posX - handleX; 199 } 200 }; 201 202 Plugin.prototype.handleMove = function(e) { 203 e.preventDefault(); 204 var posX = this.getRelativePosition(this.$range[0], e); 205 206 this.setPosition(posX - this.grabX); 207 }; 208 209 Plugin.prototype.handleEnd = function(e) { 210 e.preventDefault(); 211 this.$document.off(this.moveEvent, this.handleMove); 212 this.$document.off(this.endEvent, this.handleEnd); 213 214 var value, left, ppp; 215 216 ppp = this.getPositionFromValue(this.min + this.step); 217 value = this.getValueFromPosition(Math.round(this.position / ppp) * ppp); 218 left = this.getPositionFromValue(value); 219 220 // Update ui 221 this.$fill[0].style.width = (left + this.grabX) + 'px'; 222 this.$handle[0].style.left = left + 'px'; 223 224 this.position = left; 225 this.value = value; 226 227 if (this.onSlideEnd && typeof this.onSlideEnd === 'function') { 228 this.onSlideEnd(this.position, this.value); 229 } 230 }; 231 232 Plugin.prototype.cap = function(pos, min, max) { 233 if (pos < min) { return min; } 234 if (pos > max) { return max; } 235 return pos; 236 }; 237 238 Plugin.prototype.setPosition = function(pos) { 239 240 var value, left, ppp; 241 242 var tobe = ((this.getValueFromPosition(this.cap(pos, 0, this.maxHandleX)) / this.step) * this.step); 243 244 if (tobe >= this.lockvalue && this.lockvalue > -1) { 245 value = this.lockvalue; 246 left = this.getPositionFromValue(this.lockvalue); 247 } else { 248 // Moving steps 249 ppp = this.getPositionFromValue(this.min + this.step); 250 value = this.getValueFromPosition(Math.round(pos / ppp) * ppp); 251 left = this.cap(pos, 0, this.maxHandleX); 252 } 253 254 //Snapping steps 255 // value = (this.getValueFromPosition(this.cap(pos, 0, this.maxHandleX)) / this.step) * this.step; 256 // left = this.getPositionFromValue(value); 257 258 // Update ui 259 this.$fill[0].style.width = (left + this.grabX) + 'px'; 260 this.$handle[0].style.left = left + 'px'; 261 this.setValue(value); 262 263 // Update globals 264 this.position = left; 265 this.value = value; 266 267 if (this.onSlide && typeof this.onSlide === 'function') { 268 this.onSlide(left, value); 269 } 270 271 }; 272 273 Plugin.prototype.getPositionFromNode = function(node) { 274 var i = 0; 275 while (node !== null) { 276 i += node.offsetLeft; 277 node = node.offsetParent; 278 } 279 return i; 280 }; 281 282 Plugin.prototype.getRelativePosition = function(node, e) { 283 return (e.pageX || e.originalEvent.clientX || e.originalEvent.touches[0].clientX || e.currentPoint.x) - this.getPositionFromNode(node); 284 }; 285 286 Plugin.prototype.getPositionFromValue = function(value) { 287 var percentage, pos; 288 percentage = (value - this.min)/(this.max - this.min); 289 pos = percentage * this.maxHandleX; 290 return pos; 291 }; 292 293 Plugin.prototype.getValueFromPosition = function(pos) { 294 var percentage, value; 295 percentage = ((pos) / (this.maxHandleX || 1)); 296 value = this.step * Math.round((((percentage) * (this.max - this.min)) + this.min) / this.step); 297 return Number((value).toFixed(2)); 298 }; 299 300 Plugin.prototype.setValue = function(value) { 301 if (value !== this.value) { 302 this.$element.val(value).trigger('change', {origin: pluginName}); 303 } 304 }; 305 306 Plugin.prototype.destroy = function() { 307 this.$document.off(this.startEvent, '#' + this.identifier, this.handleDown); 308 this.$element 309 .off('.' + pluginName) 310 .removeAttr('style') 311 .removeData('plugin_' + pluginName); 312 313 // Remove the generated markup 314 if (this.$range && this.$range.length) { 315 this.$range[0].parentNode.removeChild(this.$range[0]); 316 } 317 318 // Remove global events if there isn't any instance anymore. 319 pluginInstances.splice(pluginInstances.indexOf(this.$element[0]),1); 320 if (!pluginInstances.length) { 321 this.$window.off('.' + pluginName); 322 } 323 }; 324 325 // A really lightweight plugin wrapper around the constructor, 326 // preventing against multiple instantiations 327 $.fn[pluginName] = function(options) { 328 329 330 var args = [].slice.call(arguments); 331 332 333 return this.each(function() { 334 var $this = $(this), 335 data = $this.data('plugin_' + pluginName); 336 337 // Create a new instance. 338 if (!data) { 339 $this.data('plugin_' + pluginName, (data = new Plugin(this, options))); 340 pluginInstances.push(this); 341 } 342 343 // Make it possible to access methods from public. 344 // e.g `$element.lrc('method');` 345 if (typeof options === 'string') { 346 data[args.shift()](args); 347 } 348 }); 349 }; 350 })); 351}); 352 353 354var lrc_loan_selector = 'form.lrc_form'; 355var lrc_slider_selector = 'div.range'; 356 357function lrcCalculate(e) { 358 359 /* Change relevent element's output value */ 360 var $ = jQuery, 361 form = $(this).closest(lrc_loan_selector), 362 rates = lrc__rates[form.attr('id')], 363 sliders = form.find(lrc_slider_selector), 364 p = form.find(lrc_slider_selector).filter('.lrc-slider-principal'), 365 t = form.find(lrc_slider_selector).filter('.lrc-slider-term'), 366 principal = parseFloat(p.find('input[type=range]').val()) || 0, 367 term = parseFloat(t.find('input[type=range]').val()) || ((!rates.periodslider)? 1:0); 368 369 if ($(this).hasClass("output")) return; 370 371 /* Output Principal */ 372 373 p.find('output').text(rates.cb+principal.toString().lrc_rounding(rates)+rates.ca); 374 375 /* Output term */ 376 377 periodlabel = term == 1 ? rates.singleperiodlabel : rates.periodlabel; 378 379 t.find('output').text(term+' '+periodlabel); 380 381 form.find('.lrc-outputs').show(); 382 383 /* Everything below this point should happen no matter WHICH slider is moved */ 384 385 var outputs = []; 386 387 var currencyspace = rates.currencyspace ? ' ' : ''; 388 rates.cb = rates.ca = ''; 389 390 if (rates.currency == false) rates.currency = ''; 391 if (rates.ba == 'before') rates.cb = rates.currency+currencyspace; 392 if (rates.ba == 'after') rates.ca = currencyspace+rates.currency; 393 interest = principal > rates.trigger ? rates.secondary : rates.primary; 394 395 var creditscore = $("input[name='creditselector']:checked").val(); 396 var rating = rates.usecreditselector ? rates.triggers[creditscore].name : 'N/A'; 397 var low = rates.usecreditselector ? rates.triggers[creditscore].low : interest; 398 399 outputs.push(lrc_amortization(term, principal, rates, low)); 400 401 if (rates.adminfeewhen == 'afterinterest') { 402 lrc_adminfee_after(rates,outputs,term); 403 } 404 405 /* Display the Outputs */ 406 form.find('.rate').text(low+'%'); 407 form.find('.interest').text(rates.cb+lrc_doubledigit(outputs[0].interest,rates).lrc_rounding(rates)+rates.ca); 408 form.find('.grandtotal').text(rates.cb+lrc_doubledigit(outputs[0].total,rates).lrc_rounding(rates)+rates.ca); 409 form.find('.repayment').text(rates.cb+lrc_doubledigit(outputs[0].repayment,rates).lrc_rounding(rates)+rates.ca); 410 form.find('.rating').text(rating); 411 form.find('.principal').text(rates.cb+lrc_doubledigit(principal,rates).lrc_rounding(rates)+rates.ca); 412 form.find('.term').text(term+' '+periodlabel); 413 form.find('.processing').text(rates.cb+lrc_doubledigit(outputs[0].processing,rates).lrc_rounding(rates)+rates.ca); 414 415 /* Fill the data into the hidden fields */ 416 form.find('input[name=repayment]').val(lrc_doubledigit(outputs[0].repayment,rates)
416) 417 form.find('input[name=totalamount]').val(lrc_doubledigit(outputs[0].total,rates)) 418} 419 420function lrc_doubledigit(num,rates) { 421 422 if (rates.decimals == 'none') return Math.round(num).toString(); 423 var n = num.toFixed(2); 424 if (rates.decimals == 'float') return n.replace('.00',''); 425 return n; 426 427} 428 429function lrc_adminfee(rates,P,T) { 430 431 var termfee = 0, adminfee = 0; 432 if (rates.adminfee && rates.adminfeewhen == 'beforeinterest') { 433 adminfee = P * (rates.adminfeevalue * .01); 434 if (rates.adminfeetype != 'percent') { 435 adminfee = rates.adminfeevalue; 436 } 437 } 438 if (rates.termfee && rates.adminfeewhen == 'beforeinterest') { 439 termfee = T * (rates.termfeevalue); 440 adminfee = adminfee + termfee; 441 } 442 443 if (adminfee && adminfee < rates.adminfeemin && rates.adminfeemin != false) adminfee = rates.adminfeemin; 444 if (adminfee && adminfee > rates.adminfeemax && rates.adminfeemax != false) adminfee = rates.adminfeemax; 445 446 return {'total':P+adminfee,'processing':adminfee}; 447} 448 449function lrc_adminfee_after(rates,outputs,T) { 450 451 var adminfee = 0, termfee = 0; 452 453 for (i in outputs) { 454 P = outputs[i].total; 455 456 if (rates.adminfee && rates.adminfeewhen == 'afterinterest') { 457 adminfee = P * (rates.adminfeevalue * .01); 458 if (rates.adminfeetype != 'percent') { 459 adminfee = rates.adminfeevalue; 460 } 461 } 462 if (rates.termfee && rates.adminfeewhen == 'afterinterest') { 463 termfee = T * (rates.termfeevalue); 464 adminfee = adminfee + termfee; 465 } 466 467 if (adminfee < rates.adminfeemin && rates.adminfeemin != false) adminfee = rates.adminfeemin; 468 if (adminfee > rates.adminfeemax && rates.adminfeemax != false) adminfee = rates.adminfeemax; 469 470 outputs[i].total = P + adminfee; 471 outputs[i].processing = adminfee; 472 } 473} 474 475function lrc_amortization(term, principal, rates, low) { 476 477 var preP= lrc_adminfee(rates,principal,term); 478 var P = preP.total; // Principal 479 var T = term; // Term 480 var I = low || 0; 481 var C = lrc_rterm (rates); // Adjust number of repayment periods 482 var V = lrc_term (rates); // Adjust number of periods 483 var Q = T * C/V; // 484 var A = rates.nominalapr ? low * .01 / C : Math.pow(1 + I * .01,1/C) -1; 485 if (A == 0) { 486 var M = P/Q; 487 } else { 488 var M = (A * P) / (1 - Math.pow(1 + A,-Q)) || 0; 489 } 490 var R = M * Q; 491 492 return {'repayment':M,'total':R,'interest':R-principal,'processing':preP.processing,'actual':A}; 493} 494 495function lrc_term (rates) { 496 497 var P = rates.period.toLowerCase(); 498 if (P == 'years') var C = 1; 499 if (P == 'months') var C = 12; 500 if (P == 'weeks') var C = 52; 501 if (P == 'days') var C = 365; 502 return C; 503} 504 505function lrc_rterm (rates) { 506 507 var P = rates.repaymentperiod.toLowerCase(); 508 if (P == 'ryears') var C = 1; 509 if (P == 'rmonths') var C = 12; 510 if (P == 'rweeks') var C = 52; 511 if (P == 'rdays') var C = 365; 512 return C; 513} 514 515function lrc_apply_all() { 516 517 var $ = jQuery; 518 519 /* Select all relevant loan slider forms */ 520 $(lrc_loan_selector).each(function() { 521 /* Initialize sliders */ 522 var sliders = $(this).find('[data-lrc]'), x = $(this); 523 524 sliders.change(lrcCalculate); 525 sliders.lrc({polyfill:false}); 526 527 var form = $(this), 528 rates = lrc__rates[form.attr('id')], 529 buttons = form.find('.circle-control'); 530 531 buttons.filter('.lrc-down').click(function() { 532 533 var range = $(this).closest('.range').find('input[type=range]'); 534 var v = parseFloat(range.val()); 535 var s = parseFloat(range.attr('step')) || 1; 536 var m = parseFloat(range.attr('min')); 537 var n = v - s; 538 if (n < m) range.val(m); 539 else range.val(n); 540 541 range.change(); 542 543 }); 544 545 buttons.filter('.lrc-up').click(function() { 546 547 var range = $(this).closest('.range').find('input[type=range]'); 548 var v = parseFloat(range.val()); 549 var s = parseFloat(range.attr('step')) || 1; 550 var m = parseFloat(range.attr('max')); 551 var n = v + s; 552 if (n > m) range.val(m); 553 else range.val(n); 554 555 range.change(); 556 557 }); 558 559 form.find('input[name=creditselector]').change(function() { 560 $(sliders[0]).change(); 561 }); 562 563 form.find('select').change(function() { 564 $(sliders[0]).change(); 565 }); 566 567 $(sliders[0]).change(); 568 569 }); 570 571} 572jQuery(document).ready(function($) { 573
574 lrc_apply_all(); 575 576}); 577 578String.prototype.lrc_rounding = function(rates) { 579 580 var rr = rates.rounding; 581 var r = 1; 582 583 if (rr == 'tenround') var r = 10; 584 if (rr == 'hundredround') var r = 100; 585 if (rr == 'thousandround') var r = 1000; 586 if (rr == 'noround') var num = this; 587 else var num = Math.round(this / r) * r; 588 589 var rs = rates.separator; 590 591 if (rs == 'none') return num; 592 if (rs == 'apostrophe') var s = "'"; 593 else if (rs == 'dot') var s = "."; 594 else if (rs == 'comma') var s = ","; 595 else var s = ' '; 596 var str = num.toString().split('.'); 597 if (str[0].length >= 4) { 598 str[0] = str[0].replace(/(\d)(?=(\d{3})+$)/g, '$1'+s); 599 } 600 if (rs == 'dot' || rates.decimalcomma) var decimalsdevider = ','; 601 else var decimalsdevider = '.'; 602 return str.join(decimalsdevider); 603}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.