Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found

Submitted

Mobile First | Some fixed but some of these can't understand

BBualdo 540

@BBualdo

Desktop design screenshot for the Age calculator app coding challenge

This is a solution for...

  • HTML
  • CSS
  • JS
2junior
View challenge

Design comparison


SolutionDesign

Solution retrospective


Hi, I fixed some problems with the code. It's still not perfect but that is the road of progress :)

Thank you!

Community feedback

@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
  );
};
  1. The function takes a text string as an argument, which represents a date in the format "YYYY-MM-DD".
  2. 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.
  3. 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.).
  4. 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

1

BBualdo 540

@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!

1

@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

1
BBualdo 540

@BBualdo

Posted

@JAleXDesigN I got this, thanks for help and see u in the future projects! :D

0

Please log in to post a comment

Log in with GitHub
Discord logo

Join our Discord community

Join thousands of Frontend Mentor community members taking the challenges, sharing resources, helping each other, and chatting about all things front-end!

Join our Discord