Skip to content

Instantly share code, notes, and snippets.

@RhysC
Last active December 18, 2015 04:48
Show Gist options
  • Save RhysC/5727547 to your computer and use it in GitHub Desktop.
Save RhysC/5727547 to your computer and use it in GitHub Desktop.
Knockout bindable date selector view model using moment to assist with date parameter and ensuring valid dates are shown. This code only shows valid dates in the date range supplied. Dependencies : knockout, moment
/// <reference path="~/Scripts/ThirdParty/knockout-2.1.0.debug.js" />
/// <reference path="~/Scripts/ThirdParty/moment.js" />
function YearSelector(earliestDate, latestDate) {
var self = this;
self.earliestDate = earliestDate;
self.latestDate = latestDate;
var getYears = function () {
var years = [];
for (var i = latestDate.year() ; i >= earliestDate.year() ; i--) {
years.push(i);
}
return years;
};
self.Years = getYears();
self.Year = ko.observable(self.Years[0]);
self.SetDate = function (year) {
if (year < self.Years[self.Years.length - 1]) {
self.Year(self.Years[self.Years.length - 1]);
} else {
if (year > self.Years[0]) {
self.Year(self.Years[0]);
} else {
self.Year(year);
}
}
};
return self;
}
function MonthYearSelector(earliestDate, latestDate) {
YearSelector.call(this, earliestDate, latestDate);
var self = this;
//NOTE these are .Net values, not javascript values, (Jan in js is 0, whereas in .Net it is 1)
self.RawMonths = [{ Display: "January", Value: 1 },
{ Display: "February", Value: 2 },
{ Display: "March", Value: 3 },
{ Display: "April", Value: 4 },
{ Display: "May", Value: 5 },
{ Display: "June", Value: 6 },
{ Display: "July", Value: 7 },
{ Display: "August", Value: 8 },
{ Display: "September", Value: 9 },
{ Display: "October", Value: 10 },
{ Display: "November", Value: 11 },
{ Display: "December", Value: 12 }];
self.Month = ko.observable();
self.Months = ko.computed(function () {
if (self.Year() == self.earliestDate.year()) {
if (self.Month() < self.earliestDate.month() + 1) {
self.Month(self.earliestDate.month() + 1);
}
return self.RawMonths.slice(self.earliestDate.month());
}
if (self.Year() == self.latestDate.year()) {
if (self.Month() > self.latestDate.month() + 1) {
self.Month(self.latestDate.month() + 1);
}
return self.RawMonths.slice(0, self.latestDate.month());
}
return self.RawMonths;
});
self.Month(self.Months()[0].Value);
return self;
}
function DateSelector(earliestDate, latestDate) {
MonthYearSelector.call(this, earliestDate, latestDate);
var self = this;
self.RawDays = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31];
self.Day = ko.observable();
self.Days = ko.computed(function () {
if (self.Year() == self.latestDate.year() && self.Month() == (self.latestDate.month() + 1)) {
if (self.Day() > self.latestDate.date()) {
self.Day(self.latestDate.date());
}
return self.RawDays.slice(0, self.latestDate.date());
}
if (self.Year() == self.earliestDate.year() && self.Month() == (self.earliestDate.month() + 1)) {
if (self.Day() < self.earliestDate.date()) {
self.Day(self.earliestDate.date());
}
return self.RawDays.slice(self.earliestDate.date() - 1);
}
var daysInMonth = moment([self.Year(), self.Month() - 1, 1]).daysInMonth();
if (self.Day() > daysInMonth) {
self.Day(daysInMonth);
}
return self.RawDays.slice(0, daysInMonth);
});
self.Day(self.Days()[0]);
self.DateText = ko.computed({
read: function () {
return moment([self.Year(), self.Month() - 1, self.Day()]).format('DD/MM/YYYY');//http://momentjs.com/docs/#/displaying/format/
},
//write allows us
write: function (value) {
var attemptedDate = moment(value, 'DD/MM/YYYY');
if (attemptedDate.isValid() && attemptedDate.isAfter(earliestDate) && attemptedDate.isBefore(latestDate)) {
self.Year(attemptedDate.years());
self.Month(attemptedDate.months()+1);
self.Day(attemptedDate.date());
}//else do nuffin!
},
owner: this
});
return self;
}
/// <reference path="~/Scripts/ThirdParty/jasmine/jasmine.js"/>
/// <reference path="~/Scripts/ThirdParty/knockout-2.1.0.debug.js" />
/// <reference path="~/Scripts/ThirdParty/moment.js" />
/// <reference path="~/Scripts/ViewModels/DateSelector.js" />
describe("DateSelector", function () {
describe("Default", function () {
// - 11/Mar/2010 - 5/Aug/2013
var ds = new DateSelector(moment([2010, 3, 11]), moment([2013, 7, 5]));
it("should have years in between and including earliest and latest year in Years list", function () {
for (var i = 2010; i <= 2013; i++) {
expect(ds.Years).toContain(i);
}
});
it("should have latest year first on the list", function () {
expect(ds.Years[0]).toBe(2013);
});
it("should have latest year by default", function () {
expect(ds.Year()).toBe(2013);
});
it("should have first month and day by default", function () {
expect(ds.Month()).toBe(1);
expect(ds.Day()).toBe(1);
});
});
describe("when changing to earliest year", function () {
var ds, earliestDate = moment([2010, 2, 11]);//11th of mar 2010 (js month index in 0 based)
beforeEach(function () {
ds = new DateSelector(earliestDate, moment([2013, 4, 5]));
});
describe("given select month is before earliest month", function () {
it("should set the month to earliest month", function () {
ds.Year(2012);
ds.Month(2);//FEB - ie one month earlier than the earliest month in the earliest year
ds.Year(earliestDate.years());
expect(ds.Month()).toBe(3); //.Net month index
});
});
describe("given selected day is before earliest day", function () {
it("should set the Day to earliest day", function () {
ds.Year(2012);
ds.Month(3); //earliest month
ds.Day(1);//before the earliest day for the earliest month
ds.Year(earliestDate.years());
expect(ds.Day()).toBe(earliestDate.date());
});
});
});
describe("when changing to latest year", function () {
var ds, latestdate = moment([2013, 4, 5]);//5th of Apr 2013 (js month index in 0 based)
beforeEach(function () {
ds = new DateSelector(moment([2010, 2, 11]), latestdate);
});
describe("given select month is after latest month", function () {
it("should set the month to latest month", function () {
ds.Year(2012);
ds.Month(6);//JUN - ie one month later than the latest month in the latest year
ds.Year(latestdate.years());
expect(ds.Month()).toBe(5); //.Net month index
});
});
describe("given selected day is after latest day", function () {
it("should set the Day to latest day", function () {
ds.Year(2012);
ds.Month(5); //latest month
ds.Day(6);//after the latest day for the latest month
ds.Year(latestdate.years());
expect(ds.Day()).toBe(latestdate.date());
});
});
});
describe("Days in months", function () {
it("should only show valid days in each months", function () {
var ds = new DateSelector(moment([2000, 1, 1]), moment([2020, 1, 1]));
ds.Year(2011);//non leap year
ds.Month(1);
expect(ds.Days().length).toBe(31);
ds.Month(2);
expect(ds.Days().length).toBe(28);
ds.Month(3);
expect(ds.Days().length).toBe(31);
ds.Month(4);
expect(ds.Days().length).toBe(30);
ds.Month(5);
expect(ds.Days().length).toBe(31);
ds.Month(6);
expect(ds.Days().length).toBe(30);
ds.Month(7);
expect(ds.Days().length).toBe(31);
ds.Month(8);
expect(ds.Days().length).toBe(31);
ds.Month(9);
expect(ds.Days().length).toBe(30);
ds.Month(10);
expect(ds.Days().length).toBe(31);
ds.Month(11);
expect(ds.Days().length).toBe(30);
ds.Month(12);
expect(ds.Days().length).toBe(31);
//leap year
ds.Year(2012);
ds.Month(2);
expect(ds.Days().length).toBe(29);
});
});
describe("when changing month", function () {
it("when current day is greater than last day in selected month, set day to last", function () {
var ds = new DateSelector(moment([2000, 1, 1]), moment([2020, 1, 1]));
ds.Year(2011);//non leap
ds.Month(3);
ds.Day(30);
ds.Month(2);
expect(ds.Day()).toBe(28);
});
});
describe("DateText binding", function () {
describe("DateText is read correctly", function () {
it("gives 02/06/2012", function () {
var ds = new DateSelector(moment([2000, 1, 1]), moment([2020, 1, 1]));
ds.Year(2012);
ds.Month(6);
ds.Day(2);
expect(ds.DateText()).toBe('02/06/2012');
});
});
describe("DateText is written correctly", function () {
it("given 02/06/2012 it get correct date", function () {
var ds = new DateSelector(moment([2000, 1, 1]), moment([2020, 1, 1]));
ds.DateText('02/06/2012');
expect(ds.Year()).toBe(2012);
expect(ds.Month()).toBe(6);
expect(ds.Day()).toBe(2);
});
it("given 12/06/2012 it get correct date", function () {
var ds = new DateSelector(moment([2000, 1, 1]), moment([2020, 1, 1]));
ds.DateText('12/06/2012');
expect(ds.Year()).toBe(2012);
expect(ds.Month()).toBe(6);
expect(ds.Day()).toBe(12);
});
});
describe("Invalid datetexts are not assigned", function () {
it("given invalid string it doesnt change the existing date", function () {
var ds = new DateSelector(moment([2000, 1, 1]), moment([2020, 1, 1]));
ds.Year(2012);
ds.Month(6);
ds.Day(2);
ds.DateText('33/33/2012');
expect(ds.Year()).toBe(2012);
expect(ds.Month()).toBe(6);
expect(ds.Day()).toBe(2);
});
it("given date before earliest date it doesnt change the existing date", function () {
var ds = new DateSelector(moment([2000, 1, 1]), moment([2020, 1, 1]));
ds.Year(2012);
ds.Month(6);
ds.Day(2);
ds.DateText('01/01/1999');
expect(ds.Year()).toBe(2012);
expect(ds.Month()).toBe(6);
expect(ds.Day()).toBe(2);
});
it("given date after latest date it doesnt change the existing date", function () {
var ds = new DateSelector(moment([2000, 1, 1]), moment([2020, 1, 1]));
ds.Year(2012);
ds.Month(6);
ds.Day(2);
ds.DateText('01/01/2021');
expect(ds.Year()).toBe(2012);
expect(ds.Month()).toBe(6);
expect(ds.Day()).toBe(2);
});
});
});
});
@RhysC
Copy link
Author

RhysC commented Jun 10, 2013

currently an error where the latest month is not showing when latest year is selceted. issue with line 61

return self.RawMonths.slice(0, self.latestDate.month()); // needs +1 to get correct length of array

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment