Skip to content

Instantly share code, notes, and snippets.

@Andrew-Max
Last active April 25, 2017 19:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Andrew-Max/027696e44e042dbf70b90abc43d5ea1d to your computer and use it in GitHub Desktop.
Save Andrew-Max/027696e44e042dbf70b90abc43d5ea1d to your computer and use it in GitHub Desktop.
Math Birthday
<!DOCTYPE html>
<html>
<head>
<title>Math B-Day</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.css" />
<link href="https://fonts.googleapis.com/css?family=Oswald" rel="stylesheet">
<style media="screen" type="text/css">
* {
font-family: 'Oswald', sans-serif;
}
.navbar {
background-color: #444;
border: none;
border-radius: 0;
}
.navbar p {
margin: 5px 0 0 0;
font-size: 28px;
color: #69d8a1;
}
.content-body {
margin: 10px;
}
.input-container {
margin: 10px 0 0 0;
}
.input-container input {
width: 240px;
margin-bottom: 10px;
}
.result-container {
margin-top: 20px;
display: none;
}
.result-container button.extras-show {
background: #69d8a1;
}
.result-container button.extras-show:hover {
background: #19ff8d;
}
.result-container button.extras-hide {
display: none;
}
.advanced-results {
margin-top: 20px;
display: none;
}
.input-field {
width: 240px;
position: relative;
}
.input-field .close{
position: absolute;
top: 9px;
right: 10px;
font-size: 16px;
}
.input-field .close:hover{
color: red;
}
.errors-container {
display: none;
margin-top: 20px;
}
.errors-container .error {
color: red;
}
</style>
</head>
<body>
<nav class="navbar navbar-default">
<div class="container-fluid">
<p>Math Birthday</p>
</div>
</nav>
<div class="content-body container-fluid">
<div class="row">
<div class="input-container">
<div class="input-field">
<input type="text" class="form-control" id="datepicker" name="birthday">
<span class="close">Reset</span>
</div>
<button class="btn btn-primary">Submit</button>
</div>
<div class="result-container">
<div class="main-result">
<p>Your next Math Birthday is your <span class="next-bday-days"></span>-day-old birthday in <span class="days-till"></span> days on <span class="next-bday-date"></span></p>
<button class="extras-show btn">Show Advanced Math Bday Info</button>
<button class="extras-hide btn btn-danger">Hide Advanced Math Bday Info</button>
</div>
<div class="advanced-results">
<ul>
<li>You are <span class="days-old"></span> days old.</li>
<li>Your last Math Birthday was your <span class="last-bday-days"></span>-day-old birthday</li>
<li>Your last Math Birthday was <span class="days-since-last"></span> days ago</li>
<li>Your Last Math Birthday was on <span class="last-bday-date"></span></li>
<li class="1-day"></li>
<li class="10-day"></li>
<li class="100-day"></li>
<li class="1000-day"></li>
<li class="10000-day"></li>
<li class="100000-day"></li>
</ul>
</div>
</div>
<div class="errors-container">
<p class="error"></p>
</div>
</div>
</body>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
<script type="text/javascript">
//=============================
// Main function
//=============================
$( document ).ready(function() {
initializeDatepicker();
$('.input-container button').on('click', handleBdayInput);
$('.input-container .close').on('click', handleInputReset);
$('.result-container button.extras-show').on('click', handleToggleExtras);
$('.result-container button.extras-hide').on('click', handleToggleExtras);
});
//=============================
//Data Caculation
//=============================
function ViewModel(date) {
var presets = new PresetsObj(date)
this.presets = presets;
this.nextBDay = new NextMathBDay(presets);
this.lastBDay = new LastMathBDay(presets);
this.extras = new Extras(presets);
};
function PresetsObj(rawDate) {
this.date = rawDate,
this.birthday = function() {
return moment(this.date, 'MM-DD-YYYY')
},
this.numCurrentDays = function() {
return moment().diff(this.birthday(), "days")
},
this.currentDaysCharLength = function() {
return this.numCurrentDays().toString().length;
}
};
function NextMathBDay(presetsObj) {
this.dayOfNextMathBDay = function() {
return Math.pow(10, presetsObj.currentDaysCharLength());
},
this.daysTillNextMathBDay = function() {
return this.dayOfNextMathBDay() - presetsObj.numCurrentDays();
},
this.nextMathBDayDate = function() {
var nextMathBDayDate = moment().add(this.daysTillNextMathBDay(), 'days');
return nextMathBDayDate.format("dddd, MMMM Do YYYY");
}
};
function LastMathBDay(presetsObj) {
this.dayOfLastMathBDay = function() {
return Math.pow(10, presetsObj.currentDaysCharLength() - 1);
},
this.daysSinceLastMathBDay = function() {
return presetsObj.numCurrentDays() - this.dayOfLastMathBDay();
},
this.lastMathBDayDate = function() {
var lastMathBDayDate = moment().subtract(this.daysSinceLastMathBDay(), 'days');
return lastMathBDayDate.format("dddd, MMMM Do YYYY");
}
};
function Extras(presetsObj) {
this.currentDays = function() {
return presetsObj.numCurrentDays();
},
this.BDay1 = function() {
return calcTimeDiffString(presetsObj, 1);
},
this.BDay10 = function() {
return calcTimeDiffString(presetsObj, 10);
},
this.BDay100 = function() {
return calcTimeDiffString(presetsObj, 100);
},
this.BDay1000 = function() {
return calcTimeDiffString(presetsObj, 1000);
},
this.BDay10000 = function() {
return calcTimeDiffString(presetsObj, 10000);
},
this.BDay100000 = function() {
return calcTimeDiffString(presetsObj, 100000);
}
};
function calcTimeDiffString(presetsObj, bday) {
var date = presetsObj.birthday().add(bday, "days");
var isBeforeBirthday = date.isBefore(moment());
var timeString = isBeforeBirthday ? "was " + date.fromNow() : "is " + date.fromNow();
return "Your " + bday.toString() + " day Math Birthday " + timeString + " on " + date.format("dddd, MMMM Do YYYY");
}
//=============================
// Initialization
//=============================
function initializeDatepicker() {
$("#datepicker").datepicker({
changeMonth: true,
changeYear: true,
yearRange: '-110:+0',
maxDate: new Date
});
};
//=============================
// Event Handlers
//=============================
function handleBdayInput() {
var bdayInput = $('.input-container input').val();
if(bdayInput === "") {
return throwNoInputError();
} else {
clearErrors();
}
var viewModel = new ViewModel(bdayInput);
updateDOM(viewModel);
};
function handleInputReset() {
$('.result-container').hide();
$('.input-container input').val(null);
$('.result-container button.extras-show').show();
$('.result-container button.extras-hide').hide();
$('.advanced-results').hide();
clearErrors();
};
function handleToggleExtras() {
$('.advanced-results').toggle();
$('.result-container button.extras-show').toggle();
$('.result-container button.extras-hide').toggle();
};
//=============================
//Errors
//=============================
function throwNoInputError() {
$('.errors-container').show();
$('.errors-container .error').html('Please Choose A Date Before Submitting');
};
//=============================
//Utility
//=============================
function clearErrors() {
$('.errors-container').hide();
$('.errors-container .error').html('');
};
function updateDOM(viewModel) {
$('.result-container').show();
$('.result-container span.next-bday-days').html(viewModel.nextBDay.dayOfNextMathBDay());
$('.result-container span.days-till').html(viewModel.nextBDay.daysTillNextMathBDay());
$('.result-container span.next-bday-date').html(viewModel.nextBDay.nextMathBDayDate());
$('.result-container span.days-old').html(viewModel.extras.currentDays());
$('.result-container span.last-bday-days').html(viewModel.lastBDay.dayOfLastMathBDay());
$('.result-container span.days-since-last').html(viewModel.lastBDay.daysSinceLastMathBDay());
$('.result-container span.last-bday-date').html(viewModel.lastBDay.lastMathBDayDate());
$('.result-container .1-day').html(viewModel.extras.BDay1());
$('.result-container .10-day').html(viewModel.extras.BDay10());
$('.result-container .100-day').html(viewModel.extras.BDay100());
$('.result-container .1000-day').html(viewModel.extras.BDay1000());
$('.result-container .10000-day').html(viewModel.extras.BDay10000());
$('.result-container .100000-day').html(viewModel.extras.BDay100000());
};
</script>
</html>
<!-- README
Some quick annotations on the design decisions here:
CODE
========
This isn't a master piece by any means. Right off the bat I probably wouldn't use JQuery to do heavy lifting in a production app these days. If an app was complex enough to justify it I would use something like Angular/ React / Ember. For a trivial app like this, I might just try to use Native JS for DOM manipulation to keep things lightweight. And obviously throwing everything on a single page is fine for a toy project but is not a good solution for building a production application. So this isn't representative of how I'd build this in production.
I did add a big chunk of functionality which was beyond the stated assignment which was the advanced data field. It did end up almost doubling the complexity of the JS. In a real work environment I understand that it is important to come to consensus on features and not just build stuff out beyond spec but here I was just having fun with it.
What I did try to display here:
I Kept functions small, composable, and single purpose. I choose long descriptive variable names in a preference for readability over brevity. My main aim was to make this clean and readable despite being thrown on a single page without any large overarching architecture.
The only real architectural decsion here was to create a view-model in the form of the `dataObj` which minimizes the DOM's knowledge of what is going on the JS/ Data calculations and only allows it to access a single `injected` data object.
My last concern was to make both the DOM and the JS readable. I aim to use semantic class names so the DOM is self descriptive. In the JS I used simple header comments for organization and tried to make all variable names and function names self descriptive.
UI
=========
I tried not to over design the UI and keep simplicity and usability in mind. I pulled in a font to make the text a bit nicer and used a very simple color scheme that I though was nice looking without being distracting.
I was careful to make sure all UX interactions were straightforward, involved minimal effort from the user, and had smart defaults (ie: disabling future dates). I made an effort to catch invalid submissions (submit with no data) and give feedback to the user as to what was wrong and how to correct the issue. I tried to pay atention to detail with things like resetting the state so the functionality should work correctly from any application state.
Wireframes
===================
I didn't end up producing any wireframes as requested in the project description becuase one of my major philosophies for writing code is not to invent the wheel, and in this case there were a plethora of good pre existing options. So in this sense my design work on this was not so much a whiteboarding excercise but to map out my requirements for the datepicker (simple ui, minimal clicks for year and month input, functional with touch) and then to evaluate the existing options on those criteria. I ended up finding that the api for the JQuery ui data picker was very minimal and nice to work with and met all of those requirements.
Next Feature
=============
One thing I wanted to add was a description of how Math Birthdays work for the user because I don't think that's very clear as is. Because the header is under utilized here I would probably add a item on the left of the header that opened up a down facing tool tip on click that gave a 1-2 sentence description of what a Math Birthday is.
-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment