@JAleXDesigN
Posted
Hi @BBualdo, regarding the first question, the reason why you can't directly use the value of an input in JavaScript via a global variable like for example:
const startingDay = document.querySelector('.js-day-input').value
, and expecting the value to be automatically updated in other functions may be related to when that line of code is executed.
When you define startingDay using document.querySelector('.js-day-input').value
, you are actually storing the current value of the input at that moment in the startingDay variable. However, if the value of the input changes later (for example, due to user interaction), the startingDay variable will not automatically update to reflect that change.
In the second question it seems that you are referring to the section where you check if the entered date is in the past. To handle this case along with other errors, you can bundle all error checks into a single function (like lookForErrors)
and then, within this function, call calculateDates only if all errors are hidden. This will ensure that all error checks are done before doing the calculations and animation.
To resolve the button background image, you need to fix the url to: url(../assets/images/icon-arrow.svg), and you don't need to use the url twice, if you're trying to thicken the white border of the image, you can edit the svg image with the text editor and change the stroke-width="2" property to a larger number, e.g. stroke-width="3",
As for how you could make your code cleaner, here are some suggestions:
- Instead of repeating the same code to show errors and apply styles on error, you can create a function that takes care of this, which will make your code cleaner and easier to maintain, for example:
function displayError(input, errorElement, errorMessage, label) {
errorElement.innerHTML = errorMessage;
errorElement.classList.remove("hidden");
input.classList.add("input-error");
label.classList.add("label-error");
}
function clearError(input, errorElement, label) {
errorElement.classList.add("hidden");
input.classList.remove("input-error");
label.classList.remove("label-error");
}
And you would use these functions like this:
Display error: displayError(startingDay, dayError, 'This field is required', dayLabel);
Clear Error:clearError(startingDay, dayError, dayLabel);
To check if it is a valid date you could use an auxiliary function like for example:
function isValidDate (date) => {
const [year, month, day] = date.split("-").map(Number);
const dateObj = new Date(year, month - 1, day);
return (
dateObj.getFullYear() === year &&
dateObj.getMonth() + 1 === month &&
dateObj.getDate() === day
);
};
- The function takes a text string as an argument, which represents a date in the format "YYYY-MM-DD".
date.split("-").map(Number)
splits the date string into three parts separated by hyphens ("-"). Each part represents the year, month, and day of the date. The Number function is then applied to each of these parts to convert the strings to numeric values.- The line
const dateObj = new Date(year, month - 1, day);
creates a date object using the numeric values extracted from the date string. The subtraction of 1 from the month value is because in the JavaScript Date class, months are indexed from 0 (January is 0, February is 1, etc.). - Then, three checks are performed to determine if the date is valid:
In this case if we pass a date that does not exist, for example:
isValidDate("2022-02-29") //Returns false
//the function will do this
function isValidDate (date) {
const [year, month, day] = date.split("-").map(Number);
//[2022, 02, 29]
const dateObj = new Date(year, month - 1, day);
//Being a date that is not valid because February had 28 days in that year, the result of dateObj will be: Date: "2022-03-01T05:00:00.000Z"
//When performing the validations we will have to coincide in the year but not in the month so it would return false
return (
dateObj.getFullYear() === year &&
dateObj.getMonth() + 1 === month &&
dateObj.getDate() === day
);
};
For animations you can use the requestAnimationFrame method to perform the animation instead of using setInterval, which will provide finer control over the animation, you can do it in the following way:
function calculateAnimation() {
// Select all <span> elements
let valueDisplays = document.querySelectorAll("span");
// Define a function to animate the value change
function animateValue(displayElement, start, end, duration) {
const startTime = performance.now();
// Define a function to update the animated value
function updateValue(currentTime) {
const elapsedTime = currentTime - startTime;
const progress = Math.min(elapsedTime / duration, 1);
// Calculate the animated value based on progress
const animatedValue = Math.round(start + progress * (end - start));
// Update the displayed value
displayElement.textContent = animatedValue;
// If animation is not complete, request the next animation frame
if (progress < 1) {
requestAnimationFrame(updateValue);
}
}
// Start the animation by requesting the first animation frame
requestAnimationFrame(updateValue);
}
// Iterate through each <span> element and animate its value change
valueDisplays.forEach((valueDisplay) => {
const startValue = 0;
const endValue = parseInt(valueDisplay.innerHTML);
const animationDuration = 1000; // Duration in milliseconds
// Initiate the animation for the current value display
animateValue(valueDisplay, startValue, endValue, animationDuration);
});
}
and you would call this function at the end of calculateDates():
function calculateDates() {
// Rest of the code....
calculateAnimation();
// animation
}
These are just some recommendations, I hope they help you to continue improving.
Marked as helpful
@BBualdo
Posted
@JAleXDesigN Hi, I red your feedback really carefully and added some improvements for my code, but I could only understand that thing with writing a function to display and clear errors :D I think I'm too newbie yet to understand that magic with date arrays... Didn't even know where and what should i replace with
function isValidDate (date) => {
const [year, month, day] = date.split("-").map(Number);
const dateObj = new Date(year, month - 1, day);
return (
dateObj.getFullYear() === year &&
dateObj.getMonth() + 1 === month &&
dateObj.getDate() === day
);
};
And that animation... This is totally black magic to me for now :O
But I really appreciate taking your time to review my code and I'm grateful :D
Thank you and wish you well!
@JAleXDesigN
Posted
@BBualdo You can use the isValidDate function to skip these checks:
else if (
(startingMonth.value == 4 &&
startingDay.value > 30 &&
startingDay.value < 32) ||
(startingMonth.value == 6 &&
startingDay.value > 30 &&
startingDay.value < 32) ||
(startingMonth.value == 9 &&
startingDay.value > 30 &&
startingDay.value < 32) ||
(startingMonth.value == 11 &&
startingDay.value > 30 &&
startingDay.value < 32) ||
(startingMonth.value == 2 && startingDay.value > 28) ||
(startingMonth.value == 2 &&
startingDay.value > 29 &&
startingYear.value % 4 !== 0)
) {
dayError.innerHTML = "Must be a valid date";
dayError.classList.remove("hidden");
startingDay.classList.add("input-error");
dayLabel.classList.add("label-error");
// some complicated regular-months errors...
}
The validations of the values of the inputs and if it is a valid date you can do it inside the lookForErrors function:
function lookForErrors() {
// you can create variables with short names to avoid repeating "startingDay.value", "startingMonth.value", etc...
const day = startingDay.value;
const month = startingMonth.value;
const year = startingYear.value;
// get current year
const currentYear = new Date().getFullYear();
if (day === "") {
displayError(startingDay, dayError, "This field is required", dayLabel);
} else if (day < 0 || day > 31) {
displayError(startingDay, dayError, "Must be a valid day", dayLabel);
} else {
clearError(startingDay, dayError, dayLabel);
}
// day errors
if (month === "") {
displayError(
startingMonth,
monthError,
"This field is required",
monthLabel
);
} else if (month < 0 || month > 12) {
displayError(
startingMonth,
monthError,
"Must be a valid month",
monthLabel
);
} else {
clearError(startingMonth, monthError, monthLabel);
}
// month errors
if (year === "") {
displayError(startingYear, yearError, "This field is required", yearLabel);
} else if (year > currentYear) {
//Check if the year entered in the input is greater than the current one
displayError(startingYear, yearError, "Must be in the past", yearLabel);
} else {
clearError(startingYear, yearError, yearLabel);
}
// year errors
if (
dayError.classList.contains("hidden") &&
monthError.classList.contains("hidden") &&
yearError.classList.contains("hidden")
) {
// If there are no errors
// You create a string with the format "YYYY-MM-DD"
const birthdate = `${year}-${month}-${day}`;
// Verify that the date is before the current one
if (!isPastDate(birthdate)) {
displayError(startingDay, dayError, "Must be in the past", dayLabel);
//return so that if it is not a date before the current one, it does not continue executing the code
return;
}
// If it is a previous date, then it checks if the date exists, and if so, it calculates the age, otherwise it shows the error
if (isValidDate(birthdate)) {
calculateDates(birthdate);
} else {
displayError(startingDay, dayError, "Must be a valid date", dayLabel);
}
}
}
// errors
// Auxiliary function to know if it is a past date, it verifies that the date is at least one day before the current one
const isPastDate = (date) => {
const currentDate = new Date();
currentDate.setHours(0, 0, 0, 0);
const selectedDate = new Date(date);
//Subtract one day from the current date
const oneDayInMillis = 24 * 60 * 60 * 1000; //milliseconds in a day
const previousDate = new Date(currentDate.getTime() - oneDayInMillis);
return selectedDate < previousDate;
};
In this case you would no longer need to do validations in the calculateDates function since this function is executed once it has passed all the validations in the lookForErrors function.
I hope this helps you, and if there is something you don't understand leave me an answer..
Marked as helpful
@BBualdo
Posted
@JAleXDesigN I got this, thanks for help and see u in the future projects! :D