Weekly High & Goals

Section overview

Out next step will add functionality to track the highest distance ran in the current week, along with setting a weekly goal. You'll learn how to work with dates in JavaScript, filter entries by week, find maximum values, and use localStorage to save the goal so it persists between page reloads.

Understanding weekly tracking

To track weekly data, we need to:

  1. Store entries with dates (not just distances)
  2. Filter entries to the current week
  3. Find the maximum value from this week's entries
  4. Set and save a weekly goal

Updating the entry structure

First, let's change how we store entries to include dates. Update your handleSubmit function:

function handleSubmit(event) {
  event.preventDefault();
  const entry = Number(document.querySelector('#entry').value);
  console.log(entry);

  if (!entry) return;

  // Create an entry object with distance and date
  const entryObj = {
    distance: entry,
    date: new Date()
  };

  entries.push(entryObj);
  document.querySelector('form').reset();
  addNewEntry(entry);
  calcTotal();
  calcAverage();
  calcWeeklyHigh();
}
  • const entryObj = { distance: entry, date: new Date() } creates an object with both distance and the current date/time.
  • new Date() creates a Date object representing the current moment.
  • Now each entry is an object instead of just a number, so we'll need to update other functions.

Updating addNewEntry function

Since entries are now objects, we need to update how we display them:

function addNewEntry(newEntry) {
  entriesWrapper.removeChild(entriesWrapper.firstElementChild);
  const listItem = document.createElement('li');
  // Access the distance property from the entry object
  const listValue = document.createTextNode(newEntry.distance.toFixed(1));
  listItem.appendChild(listValue);
  entriesWrapper.appendChild(listItem);
}
  • newEntry.distance accesses the distance property from the entry object.
  • The rest of the function remains the same.

Updating calculation functions

We also need to update the reducer and calcTotal functions to work with objects:

// 1. access the distance property on currentValue
function reducer(total, currentValue) {
  return total + currentValue.distance;
}

function calcTotal() {
  const totalValue = entries.reduce(reducer, 0);
  // 2. add the toFixed() method to format the total to 1 decimal place
  document.getElementById('total').innerText = totalValue.toFixed(1);
  document.getElementById('progressTotal').innerText = totalValue.toFixed(1);
}

Finally, update the calcAverage function:

function calcAverage() {
  // 1. add a conditional to check if there are entries in the array
  if (entries.length === 0) {
    document.getElementById('average').innerText = '0';
    return;
  }
  // update reduce(reducer, 0) to provide initial value of 0
  const average = (entries.reduce(reducer, 0) / entries.length).toFixed(1);
  document.getElementById('average').innerText = average;
}
  • reduce(reducer, 0) provides an initial value of 0 for the accumulator.

Getting the current week's start date

To filter entries by week, we need to find the start of the current week (Sunday or Monday). Let's create a helper function:

function getWeekStart() {
  const today = new Date();
  const day = today.getDay(); // 0 = Sunday, 1 = Monday, etc.
  const diff = today.getDate() - day; // Days to subtract to get to Sunday
  const weekStart = new Date(today.setDate(diff));
  weekStart.setHours(0, 0, 0, 0); // Set to start of day
  return weekStart;
}
  • new Date() gets the current date and time.
  • getDay() returns the day of the week (0-6, where 0 is Sunday).
  • getDate() returns the day of the month.
  • setDate(diff) sets the date to the start of the week (Sunday).
  • setHours(0, 0, 0, 0) sets the time to midnight for accurate date comparison.

Calculating weekly high

Now let's create a function to find the highest distance ran this week:

function calcWeeklyHigh() {
  const weekStart = getWeekStart();

  // Filter entries from this week
  const weekEntries = entries.filter(entry => {
    const entryDate = new Date(entry.date);
    return entryDate >= weekStart;
  });

  // Find the maximum distance
  if (weekEntries.length === 0) {
    document.getElementById('high').innerText = '0';
    return;
  }

  const weeklyHigh = Math.max(...weekEntries.map(entry => entry.distance));
  document.getElementById('high').innerText = weeklyHigh.toFixed(1);
}
  • getWeekStart() gets the start date of the current week.
  • entries.filter() creates a new array with only entries from this week.
  • new Date(entry.date) converts the stored date string back to a Date object.
  • entryDate >= weekStart checks if the entry is from this week or later.
  • Math.max(...weekEntries.map(entry => entry.distance)) finds the maximum distance.
  • The spread operator ... expands the array of distances into arguments for Math.max().
  • .map(entry => entry.distance) extracts just the distance values from the week's entries.

Setting and saving the weekly goal

Let's add functionality to set and save a weekly goal using localStorage:

function setGoal() {
  const goalInput = prompt('Enter your weekly goal in miles:');
  if (goalInput && !isNaN(goalInput) && goalInput > 0) {
    const goal = Number(goalInput);
    localStorage.setItem('runningGoal', goal);
    document.getElementById('target').innerText = goal;
    updateProgress();
  }
}

function loadGoal() {
  const savedGoal = localStorage.getItem('runningGoal');
  if (savedGoal) {
    document.getElementById('target').innerText = savedGoal;
  } else {
    // Default goal if none is set
    document.getElementById('target').innerText = '20';
  }
}
  • prompt() shows a dialog asking the user for input.
  • localStorage.setItem('runningGoal', goal) saves the goal to browser storage.
  • localStorage.getItem('runningGoal') retrieves the saved goal.
  • loadGoal() should be called when the page loads to restore the saved goal.

Understanding localStorage

localStorage is a browser API that stores data locally on the user's computer:

  • Persists: Data remains even after closing the browser.
  • Domain-specific: Each website has its own storage.
  • String storage: Values are stored as strings (numbers are converted automatically).
  • 5-10MB limit: Can store a reasonable amount of data.

Adding a goal button to the HTML

Update your HTML to add a button for setting the goal. Add this in the progress section:

<section class="progress">
  <h3>
    Weekly target: <span id="progressTotal">0</span> /
    <span id="target">20</span> miles
  </h3>
  <!-- add button to set the weekly goal -->
  <button onclick="setGoal()">Set goal</button>
  <div class="progressCircleWrapper"></div>
</section>
  • onclick="setGoal()" calls the function when the button is clicked.

Loading the goal on page load

Add this at the end of your script to load the goal when the page loads:

// Load saved goal when page loads
loadGoal();

Complete updated JavaScript

Here's the complete updated script.js with weekly tracking:

let entries = [];
const entriesWrapper = document.querySelector('#entries');

function addNewEntry(newEntry) {
  entriesWrapper.removeChild(entriesWrapper.firstElementChild);
  const listItem = document.createElement('li');
  const listValue = document.createTextNode(newEntry.distance.toFixed(1));
  listItem.appendChild(listValue);
  entriesWrapper.appendChild(listItem);
}

function reducer(total, currentValue) {
  return total + currentValue.distance;
}

function calcTotal() {
  const totalValue = entries.reduce(reducer, 0);
  document.getElementById('total').innerText = totalValue.toFixed(1);
  document.getElementById('progressTotal').innerText = totalValue.toFixed(1);
}

function calcAverage() {
  if (entries.length === 0) {
    document.getElementById('average').innerText = '0';
    return;
  }
  const average = (entries.reduce(reducer, 0) / entries.length).toFixed(1);
  document.getElementById('average').innerText = average;
}

function getWeekStart() {
  const today = new Date();
  const day = today.getDay();
  const diff = today.getDate() - day;
  const weekStart = new Date(today.setDate(diff));
  weekStart.setHours(0, 0, 0, 0);
  return weekStart;
}

function calcWeeklyHigh() {
  const weekStart = getWeekStart();
  const weekEntries = entries.filter(entry => {
    const entryDate = new Date(entry.date);
    return entryDate >= weekStart;
  });

  if (weekEntries.length === 0) {
    document.getElementById('high').innerText = '0';
    return;
  }

  const weeklyHigh = Math.max(...weekEntries.map(entry => entry.distance));
  document.getElementById('high').innerText = weeklyHigh.toFixed(1);
}

function setGoal() {
  const goalInput = prompt('Enter your weekly goal in miles:');
  if (goalInput && !isNaN(goalInput) && goalInput > 0) {
    const goal = Number(goalInput);
    localStorage.setItem('runningGoal', goal);
    document.getElementById('target').innerText = goal;
  }
}

function loadGoal() {
  const savedGoal = localStorage.getItem('runningGoal');
  if (savedGoal) {
    document.getElementById('target').innerText = savedGoal;
  } else {
    document.getElementById('target').innerText = '20';
  }
}

function handleSubmit(event) {
  event.preventDefault();
  const entry = Number(document.querySelector('#entry').value);

  if (!entry) return;

  const entryObj = {
    distance: entry,
    date: new Date()
  };

  entries.push(entryObj);
  document.querySelector('form').reset();
  addNewEntry(entryObj);
  calcTotal();
  calcAverage();
  calcWeeklyHigh();
}

const form = document
  .querySelector('form')
  .addEventListener('submit', handleSubmit);

// Load saved goal when page loads
loadGoal();

Summary

In this lesson, you've learned how to:

  • Store dates with entries: Created entry objects with both distance and date.
  • Work with Date objects: Used JavaScript's Date API to get current dates and calculate the start of the week.
  • Filter arrays: Used the filter() method to find entries from the current week.
  • Find maximum values: Used Math.max() with the spread operator to find the highest distance.
  • Use localStorage: Saved and loaded the weekly goal using browser storage.
  • Update multiple functions: Modified existing functions to work with entry objects.

Your running tracker now tracks weekly statistics and saves goals! In the next lesson, we'll add a visual progress circle to show how close you are to your weekly goal.