Skip to content

Instantly share code, notes, and snippets.

@rgaz1962
Created January 29, 2022 11:24
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 rgaz1962/4556466b5f78a11e8d1c6e613c09c16f to your computer and use it in GitHub Desktop.
Save rgaz1962/4556466b5f78a11e8d1c6e613c09c16f to your computer and use it in GitHub Desktop.
Tuesday January 25 2022 ~ Five habit tracker - LocalStorage
<div id="about">
<div class="mySocial">
<a href="https://www.linkedin.com/in/rominamartinliberon/" target="_new" class="social">
<i class="fab fa-linkedin"></i>
</a>
<a href="https://rominamartin.github.io/" target="_new" class="social">
<i class="fab fa-github"></i>
</a>
<a href="https://twitter.com/rominamartinlib" target="_new" class="social">
<i class="fab fa-twitter"></i>
</a>
</div>
</div>
<div id="month">
<div id="previousMonth"><i class="fa fa-arrow-left"></i></div>
<h1 id="monthLabel"></h1>
<div id="nextMonth"><i class="fa fa-arrow-right"></i></div>
</div>
<div id="daysLabels"></div>
<div id="calendar"></div>
<div id="info">
<ul id="taskLabels"></ul>
<div id="taskExample" class="dayContainer"></div>
</div>
<div class="seanSocial">
<span>Props to <a href="https://seanwes.com" target="_new" class="social">Sean McCabe</a> who is the creator of <a href="https://seanwes.com/2018/five-habit-tracker-free-printable-pdf/" target="_new" class="social">Five Habit Tracker</a></span>
<a href="https://seanwes.com/" target="_new" class="social">
<img src="https://secure.gravatar.com/avatar/9f2e580277054720abc5323c48417040?s=64&d=mm&r=g" alt="linkedin"></img>
</a>
</div>
/*** CONSTANTS ***/
const CALENDAR = document.getElementById("calendar");
const MONTH_NAMES = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
const DAY_NAMES = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
const CURRENT_DATE = new Date();
let selectedMonth = CURRENT_DATE.getMonth();
let availableMonths = [CURRENT_DATE.getMonth()];
let EXAMPLE_TASKS = {
0: {color: "#d11141", label: "Wake up at 7:00"},
1: {color: "#00b159", label: "Make bed"},
2: {color: "#00aedb", label: "Dinner before 23:00"},
3: {color: "#f37735", label: "Practice Ukulele"},
4: {color: "#ffc425", label: "Go exercise"}
}
function init() {
checkLocalStorage();
createDateInfo();
createCalendarStructure();
createInfoStructure();
}
var checkLocalStorage = () => {
if(window.localStorage.currentMonth === undefined || window.localStorage.data === undefined) {
localStorage.setItem("currentMonth", CURRENT_DATE.getMonth());
let currentData = {[CURRENT_DATE.getMonth()]: {tasks: EXAMPLE_TASKS, data: {}}};
localStorage.setItem("data", JSON.stringify(currentData));
} else if(window.localStorage.currentMonth !== undefined && window.localStorage.currentMonth != CURRENT_DATE.getMonth()) {
let previousData = JSON.parse(window.localStorage.data);
if(getCurrentLocalStorageData() === undefined) {
let confirmLoadPrevious = confirm("Do you want to load your previous habits?");
let tasks = {};
if(confirmLoadPrevious && previousData[getPreviousMonth()] !== undefined)
tasks = previousData[getPreviousMonth()].tasks;
localStorage.setItem("currentMonth", CURRENT_DATE.getMonth());
let currentData = {...previousData, [CURRENT_DATE.getMonth()]: {tasks: tasks, data: {}}};
localStorage.setItem("data", JSON.stringify(currentData));
}
}
availableMonths = Object.keys(JSON.parse(window.localStorage.data));
}
var changeMonth = (month) => {
selectedMonth = month;
document.getElementById("monthLabel").innerText = MONTH_NAMES[selectedMonth];
checkMonthsAvailability();
updateTasks();
setCalendarData();
}
var updateTasks = () => {
let inputs = document.querySelectorAll("#taskLabels input[type='text']");
inputs.forEach((task, i) => {
colorSquareChanged(i);
taskNameChanged(i);
})
}
var setCalendarData = () => {
let days = document.querySelectorAll("#calendar .dayContainer");
const startDay = getMonthDayStart();
const monthDays = getDaysFromCurrentMonth();
let currentData = getCurrentLocalStorageData().data;
days.forEach((day, i) => {
let daySquare = day.children;
let dayNumber = i - startDay;
if(i < startDay || dayNumber >= monthDays) {
if(i < startDay) day.classList.add("emptyDay");
for(let square = 0; square < daySquare.length; square++) {
daySquare[square].classList.add("hidden");
};
} else if(dayNumber < monthDays) {
day.classList.remove("emptyDay");
for(let j = 0; j < daySquare.length; j++) {
daySquare[j].onclick = () => {toggleTaskActive(dayNumber, j, daySquare[j])};
daySquare[j].classList.remove("hidden");
daySquare[4].firstElementChild.innerText = j === 4 ? (dayNumber < 9 ? `0${dayNumber + 1}` : dayNumber + 1) : "";
setColorTaskBackground(daySquare[j], j, currentData[dayNumber][j]);
}
}
})
}
var createCalendarStructure = () => {
const days = getDaysFromCurrentMonth();
const startDay = getMonthDayStart();
let currentData = getCurrentLocalStorageData().data;
for(let i = 0; i < 35; i++) {
let dayContainer = document.createElement("div");
dayContainer.className = "dayContainer";
let dayNumber = i - startDay;
let dayIsPositive = i >= startDay;
if(currentData[dayNumber] === undefined && dayIsPositive)
currentData[dayNumber] = {};
for(let j = 0; j < 5; j++) {
if(dayIsPositive && (Object.keys(currentData[dayNumber]).length < 5)) {
currentData[dayNumber][j] = false;
}
let task = document.createElement("div");
let day = document.createElement("div");
task.className = j !== 4 ? "square" : "circle";
day.className = "day";
if(i >= startDay && dayNumber < days) {
day.innerText = task.className === "circle" ? (dayNumber < 9 ? `0${dayNumber + 1}` : dayNumber + 1) : "";
setColorTaskBackground(task, j, currentData[dayNumber][j]);
task.onclick = () => {toggleTaskActive(dayNumber, j, task)};
} else {
if(i < startDay) dayContainer.classList.add("emptyDay");
task.classList.add("hidden");
}
task.appendChild(day);
dayContainer.appendChild(task);
}
let thisMonthData = {[CURRENT_DATE.getMonth()]: {tasks: getCurrentLocalStorageData().tasks, data: currentData}};
let data = {...JSON.parse(window.localStorage.data), ...thisMonthData};
localStorage.setItem("data", JSON.stringify(data));
CALENDAR.appendChild(dayContainer);
}
}
var createDateInfo = () => {
checkMonthsAvailability();
DAY_NAMES.forEach(day => {
let dayLabel = document.createElement("span");
dayLabel.textContent = day;
document.getElementById("daysLabels").appendChild(dayLabel);
});
document.getElementById("monthLabel").innerText = MONTH_NAMES[selectedMonth];
}
var createInfoStructure = () => {
let loadedTasks = getCurrentLocalStorageData().tasks;
for(let i = 0; i < 5; i++) {
let label = document.createElement("li");
let input = document.createElement("input");
let labelSquare = document.createElement("input");
input.type = "text";
labelSquare.type = "color";
labelSquare.className = "squareLabel";
let task = document.createElement("div");
let day = document.createElement("div");
task.className = i !== 4 ? "square" : "circle";
day.className = "day";
if(loadedTasks[i] !== undefined) {
input.value = loadedTasks[i].label;
labelSquare.style.background = loadedTasks[i].color;
labelSquare.value = rgbToHex(loadedTasks[i].color)
task.style.background = loadedTasks[i].color;
} else {
input.value = EXAMPLE_TASKS[i].label;
labelSquare.value = rgbToHex(EXAMPLE_TASKS[i].color)
labelSquare.style.background = EXAMPLE_TASKS[i].color;
task.style.background = EXAMPLE_TASKS[i].color;
}
input.onchange = (inputData) => {taskNameChanged(i, inputData)};
labelSquare.onchange = (inputColor) => {colorSquareChanged(i, inputColor)};
task.onclick = () => {labelSquare.click();}
label.appendChild(labelSquare);
label.appendChild(input);
document.getElementById("taskLabels").appendChild(label);
task.appendChild(day);
document.getElementById("taskExample").appendChild(task);
}
}
var colorSquareChanged = (task, inputColor) => {
let current = getCurrentLocalStorageData();
let currentTasks = current.tasks;
let value = inputColor === undefined ? currentTasks[task].color : inputColor.target.value;
let currentItem = document.querySelectorAll("#taskLabels input[type='color']")[task];
value = rgbToHex(value);
currentItem.value = value;
currentItem.style.background = value;
document.getElementById("taskExample").children[task].style.background = value;
currentTasks = {...currentTasks, [task]: {color: value, label: document.querySelectorAll("#taskLabels input[type='text']")[task].value}};
let thisMonthData = {[selectedMonth]: {tasks: currentTasks, data: current.data}};
let data = {...JSON.parse(window.localStorage.data), ...thisMonthData};
localStorage.setItem("data", JSON.stringify(data));
let allTasks = document.querySelectorAll("#calendar .dayContainer:not(.emptyDay)");
current = getCurrentLocalStorageData();
allTasks.forEach((square, task) => {
let children = square.children;
for(let i = 0; i < 5; i++) {
if(current.tasks[i] !== undefined && current.data[task] !== undefined && current.data[task][i])
children[i].style.background = current.tasks[i].color;
}
})
}
var taskNameChanged = (task, inputData) => {
let current = getCurrentLocalStorageData();
let currentTasks = current.tasks;
let value = inputData === undefined ? currentTasks[task].label : inputData.target.value;
let colorContainers = getTaskSquares();
currentTasks = {...currentTasks, [task]: {color: colorContainers[task].style.background, label: value}};
document.querySelectorAll("#taskLabels input[type='text']")[task].value = value;
let thisMonthData = {[selectedMonth]: {tasks: currentTasks, data: current.data}};
let data = {...JSON.parse(window.localStorage.data), ...thisMonthData};
localStorage.setItem("data", JSON.stringify(data));
}
var getTaskStatus = (day, task) => {
let currentData = getCurrentLocalStorageData().data;
return currentData[day][task];
}
var setTaskStatus = (day, task, status) => {
let currentData = getCurrentLocalStorageData().data;
currentData[day][task] = status;
let thisMonthData = {[selectedMonth]: {tasks: getCurrentLocalStorageData().tasks, data: currentData}};
let data = {...JSON.parse(window.localStorage.data), ...thisMonthData};
localStorage.setItem("data", JSON.stringify(data));
}
var setColorTaskBackground = (element, task, completed) => {
let currentTasks = getCurrentLocalStorageData().tasks;
if(currentTasks[task] !== undefined)
element.style.background = completed ? currentTasks[task].color : "";
else
element.style.background = completed ? EXAMPLE_TASKS[task].color : "";
}
var toggleTaskActive = (day, task, element) => {
let completed = getTaskStatus(day, task);
setTaskStatus(day, task, !completed);
setColorTaskBackground(element, task, !completed);
}
var checkMonthsAvailability = () => {
let nextMonth = document.getElementById("nextMonth");
let previousMonth = document.getElementById("previousMonth");
if(!availableMonths.includes(String(getNextMonth())))
nextMonth.classList.add("hidden");
else
nextMonth.classList.remove("hidden");
if(!availableMonths.includes(String(getPreviousMonth())))
previousMonth.classList.add("hidden");
else
previousMonth.classList.remove("hidden");
}
/*** UTILS ***/
var getDaysFromCurrentMonth = () => new Date(CURRENT_DATE.getFullYear(), selectedMonth + 1, 0).getDate();
var getMonthDayStart = () => {
let startDay = new Date(CURRENT_DATE.getFullYear(), selectedMonth, 1).getDay();
return startDay === 0 ? 6 : (startDay - 1);
}
var getCurrentLocalStorageData = () => JSON.parse(window.localStorage.data)[selectedMonth];
var getTaskSquares = () => document.querySelectorAll("#taskLabels > li input[type='color']");
var getPreviousMonth = () => {
let month = selectedMonth - 1;
return month < 0 ? 11 : month;
}
var getNextMonth = () => {
let month = selectedMonth + 1;
return month > 11 ? 0 : month;
}
var rgbToHex = (rgb) => {
if(rgb.includes("#")) return rgb;
rgb = rgb.replace(/(rgb)|\(|\)/g, '')
.split(',')
.map(val => Number(val));
return rgb.reduce((acc,val) => acc + (0 + val.toString(16)).slice(-2), '#');
}
document.getElementById("nextMonth").onclick = () => {changeMonth(getNextMonth())};
document.getElementById("previousMonth").onclick = () => {changeMonth(getPreviousMonth())};
init();
$minWidth: 450px;
$borderColor: #333;
$baseTextColor: #666;
$emptyDayColor: #eee;
body {
// margin-top: 20px;
font-family: Catamaran;
text-align: center;
min-width: $minWidth;
color: $baseTextColor;
}
@mixin styling($dayContainerDim, $calendarDim, $circleDim, $circleSpace) {
#calendar {
display: grid;
justify-content: center;
grid-template: repeat(5, $dayContainerDim) / repeat(7, $dayContainerDim);
}
.dayContainer {
position: relative;
outline: 1px solid $borderColor;
margin-top: 1px;
margin-left: 1px;
display: grid;
grid-template: 50% 50% / 50% 50%;
}
.square:nth-child(1) {
border-bottom: 1px solid $borderColor;
border-right: 1px solid $borderColor;
}
.square:nth-child(2) {
border-bottom: 1px solid $borderColor;
}
.square:nth-child(3) {
border-right: 1px solid $borderColor;
}
.dayContainer .circle {
position: absolute;
background: white;
border-radius: 50%;
border: 1px solid $borderColor;
z-index: 0;
width: $circleDim;
height: $circleDim;
top: $circleDim + $circleSpace;
left: $circleDim + $circleSpace;
}
.dayContainer .circle .day {
font-size: $circleDim/2;
text-align: center;
line-height: $circleDim;
cursor: default;
user-select: none;
}
#daysLabels {
width: $calendarDim;
margin: 0 auto;
font-size: $circleDim/2;
}
#daysLabels span {
width: $dayContainerDim;
display: inline-block;
}
#month {
// width: $calendarDim;
margin: 0 auto;
display: inline;
div {
display: inline;
cursor: pointer;
}
}
#monthLabel {
margin: 0;
cursor: default;
text-shadow: 2px 2px 2px #ccc;
display: inline;
padding: 0 1em;
user-select: none;
}
#info {
width: $calendarDim;
margin: 0 auto;
}
#taskLabels {
width: $calendarDim - $dayContainerDim;
text-align: left;
list-style: none;
padding: 0;
float: left;
margin: 10px 0;
}
#taskLabels > li {
// font-size: $circleDim/2;
min-width: 230px;
}
#taskLabels > li input[type="color"] {
width: 1.5vw;
height: 1.5vw;
display: inline-block;
margin-right: 1vw;
position: relative;
top: 0;
border: 1px solid $borderColor;
cursor: pointer;
}
#taskLabels > li input {
border: none;
border-bottom: 1px solid $borderColor;
padding: 5px;
padding-bottom: 0;
outline: none;
width: 70%;
color: $baseTextColor;
font-size: 2vh;
}
#taskExample {
width: $dayContainerDim;
height: $dayContainerDim;
position: relative;
top: 50px;
cursor: pointer;
}
.emptyDay {
background: $emptyDayColor;
}
#about {
.mySocial {
position: absolute;
top: 25px;
left: 25px;
a {
display: inline-block;
height: 2.5em;
}
a i {
font-size: 1.5em;
&.fa-linkedin {
color: #0073B0;
}
&.fa-github {
color: #24292E;
}
&.fa-twitter {
color: #1DA1F2;
}
}
}
}
.seanSocial {
position: relative;
text-align: left;
margin-left: 25px;
display: inline-block;
width: 100%;
a img {
height: 1.5em;
border-radius: 50%;
position: relative;
top: 5px;
}
}
.hidden {
visibility: hidden;
}
input::-webkit-color-swatch {
border: none;
}
}
@media screen and (max-width: 900px) {
@include styling(
$dayContainerDim: 54px,
$calendarDim: 54px * 7,
$circleDim: 20px,
$circleSpace: -5px
);
}
@media screen and (min-width: 900px) {
@include styling(
$dayContainerDim: 5vw,
$calendarDim: 5vw * 7,
$circleDim: 1.5vw,
$circleSpace: 0.25vw
);
body {
overflow-x: hidden;
}
}
<link href="https://fonts.googleapis.com/css?family=Catamaran" rel="stylesheet" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/css/all.min.css" rel="stylesheet" />

Tuesday January 25 2022 ~ Five habit tracker - LocalStorage

You'll be able to keep track of five habits of your choice. You can custom each habit name and its color. You won't loose any progress made since it uses localStorage to keep it all.

If the month changes you'll have the chance to keep the same habits or modify them. You'll be able to see the previous/next data if there is as well.

This tracker is Sean McCabe idea's so you can check his page and learn more about it.

A Pen by Robert Gorman on CodePen.

License.

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