define(['Backbone'], function(){
var min = 50;
var max = 400;
return Backbone.Model.extend({
defaults: {
amount: 0,
code: null,
date: (new Date())
},
initialize: function(){
this.on('error', this.handle_errors);
this.on('change:amount', this.amount_change);
},
validate: function (attributes) {
var errors = [];
var amount = parseInt(attributes.amount, 10);
/*
We first parse the amount (as it comes through as a String)
Once it's converted to a number we check that the value is NaN.
We also check if the amount entered fits within the allowed range.
*/
if (_.isNaN(amount) || amount < min || amount > max) {
errors.push({
field: 'amount',
error: 'The amount was invalid'
});
}
if (errors.length) {
return errors
}
},
handle_errors: function (model, error) {
// Context of 'this' gets lost within _.each()
var self = this;
_.each(error, function (item, iterator) {
self.trigger('item:invalid', item);
});
},
amount_change: function(){
this.trigger('amount:changed');
}
});
});
View.js
define(['../Utils/Templating/hogan', '../Utils/Datepicker/kalendae', '../Models/LoanApplication', '../Utils/DOM/getEl', 'Backbone'], function (hogan, Kalendae, LoanApplication, getElement) {
// ES3, ES5 non strict
var global = (function(){return this})();
return Backbone.View.extend({
initialize: function(){
// Store other elements that will be interacted with.
// Any element that will potentially utilise jQuery we pre-wrap in a single jQuery instance.
this.promocode = getElement('js-promocode');
this.amount = getElement('js-amount');
this.error_amount = $('#js-amounterror');
this.error_amount_popup = $('#js-amounterror-popup');
this.popup = $('#js-loanpopup');
this.popup_amount = getElement('js-popupamount');
// We use this to tell whether the calendar widget has already been rendered,
// as there is no point re-rendering it every time the popup is closed then opened again.
this.is_calendar_rendered = false;
// Store the Model object for easy reference
this.model = new LoanApplication();
// The Model triggers custom events when certain actions happen which the View should ideally handle
this.model.on('item:invalid', this.process_errors, this);
this.model.on('amount:changed', this.remove_error, this);
},
// The containing element
el: getElement('js-loanapplication'),
// Selectors are scoped to the parent element
events: {
'focus #js-pickdate': 'validate_amount',
'click #js-calendarclose': 'close_popup',
'click #js-applynow': 'validate_amount'
},
validate_amount: function(){
// We validate a different field depending on whether the popup is open (the popup has its own copy of the application fields)
// Note: Model's "set" method calls Backbone validation by default (see Model for validation rules)
// If the popup is NOT visible
if (this.popup.hasClass('hide')) {
this.model.set({
amount: this.amount.value
});
} else {
this.model.set({
amount: this.popup_amount.value
});
}
},
process_errors: function (item) {
// Check what field was invalid and display corresponding error message
switch (item.field) {
case 'amount':
// We display the error message in a different place depending on whether the popup is open
// If the popup is NOT visible
if (this.popup.hasClass('hide')) {
this.error_amount.removeClass('invisible');
} else {
this.error_amount_popup.removeClass('invisible');
}
break;
}
},
remove_error: function(){
// We remove the error message from different places depending on whether the popup is open
// If the popup is NOT visible
if (this.popup.hasClass('hide')) {
this.error_amount.addClass('invisible');
this.display_calendar();
} else {
this.error_amount_popup.addClass('invisible');
// NOW PROCESS THE APPLICATION!
}
},
display_calendar: function(){
// We only load the calendar on screens large enough to display it
// And we make sure to only render it once by check "is_calendar_rendered" is false
if (document.documentElement.clientWidth >= 585 && !this.is_calendar_rendered) {
this.render_calendar();
}
this.popup_amount.value = this.amount.value; // pass through the value into this new popup view
this.popup.removeClass('hide');
},
render_calendar: function(){
this.is_calendar_rendered = true;
// the following variables are used for calculating the difference between
// today's date and the selected date to pay back the loan
var calendar_container = getElement('js-calendar');
var curent_date = new Date();
var current_day = curent_date.getDate();
var current_month = curent_date.getMonth();
var current_year = curent_date.getFullYear();
var today, calendar;
// we correct current_month to include a zero prefix if the number is less than 10
current_month = (current_month < 10) ? ('0' + current_month) : current_month;
// construct a date for today which is used for calculating diff
today = new Date(current_year, current_month, current_day);
calendar = new Kalendae({
// element to attach the calendar to
attachTo: calendar_container,
// blackout days after 45 days from current date
blackout: function (date) {
return Kalendae.moment().yearDay() + 45 < date.yearDay(); // yearDay() is an extension Kalendae adds to moment.js to calculate the total number of days since epoch.
},
// how many characters from the week day name to display (e.g. we've gone with 3 = Mon, Tue, Wed, Thu, Fri, Sat, Sun)
columnHeaderLength: 3,
// restricts date selectability to past or future ('future' blacks out all days previous to current date)
direction: 'future',
// only allows selection of one day
mode: 'single',
// determines the number of months to display
months: 2,
// determines when the week should start (Sunday = 0 [default] or Monday = 1 etc)
weekStart: 1,
// causes the <input> to update to the selected date
subscribe: {
'change': function(){
var selected_date = this.getSelected();
var temp_integer_month;
var one_day;
var payback_date;
days_to_pay = selected_date.split('-');
// the date is returned as non-zero index format, so put it back to be zero-indexed
temp_integer_month = parseInt(days_to_pay[1], 10);
days_to_pay[1] = '0' + --temp_integer_month;
one_day = 24*60*60*1000; // hours * minutes * seconds * milliseconds
payback_date = new Date(days_to_pay[0], days_to_pay[1], days_to_pay[2]);
days_to_pay = Math.abs((today.getTime() - payback_date.getTime()) / (one_day));
// update the <input> #js-choosepaydate (currently sitting behind the popup) to display the date selected by the user
//paydate.value = selected_date;
// call function which will pull in the relevant template and populate with relevant costs
//calculate();
console.log(selected_date);
}
}
});
},
close_popup: function(){
this.popup.addClass('hide');
this.amount.value = ''; // we reset the value so the Model's "change" event can be fired (which is what we rely upon to trigger the popup)
}
});
});