Frontend Masters Boost RSS Feed https://frontendmasters.com/blog Helping Your Journey to Senior Developer Mon, 22 Apr 2024 16:09:37 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.2 225069128 Faces.js and Playing with Densely Packed Grids https://frontendmasters.com/blog/faces-js-and-playing-with-densely-packed-grids/ https://frontendmasters.com/blog/faces-js-and-playing-with-densely-packed-grids/#respond Mon, 22 Apr 2024 16:09:35 +0000 https://frontendmasters.com/blog/?p=1757 I only just recently saw Faces.js, a library for producing controllable cartoony avatars. It was launched in 2012, so I’m a little late to the party. It produces faces (busts, really) randomly, or with certain parameters locked to what you want.

# Generate a random face that always has blue skin
const face = generate({ body: { color: "blue" } });
display("my-div-id", face);

I think that’s a really cool idea, and if you needed this kind of thing on a project, you can install it yourself which is safer than using a hosted service for random avatars. Like, Pravatar is neat, but these services have a super high churn rate. Do not count on it sticking around.

I wanted to have a quick play and have it output a bunch of random faces on a grid. First I made 100 <div>s. You could do this any number of ways, including JavaScript, but I did it with Pug just for fun:

.faces
  - let i = 1;
  - while (i < 100)
    div(id=`face-${i}` class="face")
    - i++;

Then I made that into a grid:

.faces {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
}
.face {
  width: 100%
  aspect-ratio: 1 / 1;
}

Fun:

Hm. That makes me want to make the grid more interesting to look at, perhaps with some of the avatars spanning two columns (and thus two rows accommodate the height).

To do that I put a random number 1-10 on each face when creating the <div>s, as a data-* attribute:

.faces
  - let i = 1;
  - while (i < 100)
    - var n = Math.floor(Math.random() * 10 + 1);
    div(id=`face-${i}` class="face" data-size=n)
    - i++;

Then I can select some of them and make them bigger:

.face {
  width: 100%
  aspect-ratio: 1 / 1;
  
  &[data-size="5"] {
    grid-column: span 2;
    grid-row: span 2;
    scale: 1.2;
  }
}

I used scale there to juice the size even more and make them overlap:

I wanted to do a few more thing though. One, this left some gaps in the grid, as in, literal blank grid positions.

It’s incredibly satisfying to fill those in with just one extra grid declaration:

grid-auto-flow: dense;

Then I wanted the edges of the screen to allow for overlap, so the edges don’t seem perfectly lined up. I wanted it to look more like randomly cut wrapping paper. To do that, all I did was scale up the body (much like we did with the enlarged avatars) and then clip off the horizontal overlow.

body {
  scale: 1.2;
  overflow-x: clip;
}

I added a few more random sizes and scalings and, I dunno, it just ended up a satisfying little thing to play with.

Have you actually used a random avatar generator in a production app? On CodePen we just use a consistent generic one, which I hope kinda encourages people to replace it. I imagine random avatars are more useful in mockups and during development.

]]>
https://frontendmasters.com/blog/faces-js-and-playing-with-densely-packed-grids/feed/ 0 1757
Proposal for Signals https://frontendmasters.com/blog/proposal-for-signals/ https://frontendmasters.com/blog/proposal-for-signals/#comments Thu, 04 Apr 2024 14:37:58 +0000 https://frontendmasters.com/blog/?p=1564 The JavaScript architectural pattern of “signals” has really had a moment in the last year. There are lots of frameworks that have adopted it as the predominant way to declare and respond to variables that change across componentry.

Web standards bodies, at their best, notice things like this and step in to help bring them to the web platform where everyone will benefit. For JavaScript, that’s TC39, and there is a brand new proposal for signals by Rob Eisenberg and Daniel Ehrenberg. Cool!

You never know where these “Stage 0” proposals are going to go though. There is massive evidence that people like types in JavaScript, but the proposal to standardize that is two years old now and hasn’t gone anywhere.

]]>
https://frontendmasters.com/blog/proposal-for-signals/feed/ 1 1564
DOM to PNG Directly in the Browser https://frontendmasters.com/blog/dom-to-png-directly-in-the-browser/ https://frontendmasters.com/blog/dom-to-png-directly-in-the-browser/#comments Thu, 14 Mar 2024 01:51:48 +0000 https://frontendmasters.com/blog/?p=1232 You could design something on the web then take a screenshot of it. That is, in a basic sense, converting DOM to PNG. But a screenshot is rather manual and finicky. If you had to do this over and over, or you needed a very exact size (like a social media card), you can actually produce a PNG right from the browser with the click of a button. No backend service needed, like Puppeteer or Playwright which are often thought of for this type of automatic screenshotting job.

The trick is that you can render DOM to a <canvas>, thanks to the very fancy html2canvas library. Then you can use .toDataURL() to get it into PNG format and ultimately download it. I always reference Andrew Walpole’s Pen to see it done, which also happens to be a rather elegant Petite Vue example, and the contenteditable usage is a clever way to make it even more usable.

]]>
https://frontendmasters.com/blog/dom-to-png-directly-in-the-browser/feed/ 1 1232
Eloquent JavaScript (4th edition) https://frontendmasters.com/blog/eloquent-javascript-4th-edition/ https://frontendmasters.com/blog/eloquent-javascript-4th-edition/#respond Tue, 12 Mar 2024 23:54:17 +0000 https://frontendmasters.com/blog/?p=1219 An awful lot of JavaScript developers I know speak of Eloquent JavaScript as a very formative book for them in their path toward becoming the developer they are today. It’s certainly on my bookshelf. Eloquent JavaScript is now in it’s 4th edition, and available free online. Pretty sweet complementary learning resource right there, goes nicely with all the Frontend Masters books online.

]]>
https://frontendmasters.com/blog/eloquent-javascript-4th-edition/feed/ 0 1219
Building a TODO App from Scratch — Step 5 — Extra Functionality https://frontendmasters.com/blog/building-a-todo-app-from-scratch-step-5-extra-functionality/ https://frontendmasters.com/blog/building-a-todo-app-from-scratch-step-5-extra-functionality/#respond Sat, 09 Mar 2024 00:23:53 +0000 https://frontendmasters.com/blog/?p=1169 We left off in a decent place, but there was some missing functionality that we wanted all along.

  1. We need to be able to view completed to-dos (and be able to delete them entirely)
  2. We need to be able edit to-dos.

Article Series

Editing To-Dos

The interaction we decided on for editing is to double-click the todo. This will turn the to-do, in place, into an editable input. Then you hit enter (e.g. submit) or leave the input (e.g. blur) and it will save the changes.

We can use a bit of event delegation to set up this event:

list.addEventListener("dblclick", (event) => {
  const listItem = event.target.closest("li");

  // If already editing, let it be.
  if (listItem.classList.contains("editing")) return;

  listItem.classList.add("editing");

  // Do editing.
});

Now anywhere you double-click on the list will set the relevant list item into “editing mode”, here indicated by just adding a class we can style against, like hide the existing text.

More importantly, we need to insert some new HTML turning the text into an editable input. We can use a template literal of a <form> to inject as needed, like so:

list.addEventListener("dblclick", (event) => {
  const listItem = event.target.closest("li");

  // If already editing, let it be.
  if (listItem.classList.contains("editing")) return;

  listItem.classList.add("editing");
  const textItem = listItem.querySelector(".text");
  listItem.insertAdjacentHTML(
    "beforeend",
    `<form onsubmit="updateTodo(event);" class="form-edit">
       <input onblur="updateTodo(event);" type="text" class="input-edit" value="${textItem.textContent}">
     </form>`
  );
});

That calls an updateTodo() event we’ll have to write. But first, let’s make sure we focus the input and put the cursor at the end. Just a bit of nice UX right?

list.addEventListener("dblclick", (event) => {
  const listItem = event.target.closest("li");

  // If already editing, let it be.
  if (listItem.classList.contains("editing")) return;

  listItem.classList.add("editing");
  const textItem = listItem.querySelector(".text");
  listItem.insertAdjacentHTML(
    "beforeend",
    `<form onsubmit="updateTodo(event);" class="form-edit"><input onblur="updateTodo(event);" type="text" class="input-edit" value="${textItem.textContent}"></form>`
  );

  const input = listItem.querySelector(".input-edit");
  input.focus();

  // put cursor at end of input
  input.setSelectionRange(input.value.length, input.value.length);
});

Updating the to-do is pretty straightforward. We get our hands on the new text, update it in the DOM, toggle the editing class, and write the data back to localStorage. It looks like a lot of lines, but a lot of it is just getting our hands on DOM elements and basic manipulation.

function updateTodo(event) {
  event.preventDefault();
  const listItem = event.target.closest("li");
  const textItem = listItem.querySelector(".text");
  const inputItem = listItem.querySelector(".input-edit");
  const form = listItem.querySelector(".form-edit");
  textItem.textContent = inputItem.value;
  listItem.classList.remove("editing");
  form.remove();
  TODOs = TODOs.map((todo) => {
    if (todo.id === listItem.id) {
      todo.title = inputItem.value;
    }
    return todo;
  });
  localStorage["data"] = JSON.stringify(TODOs);
}

And it works!

Viewing Completed To-Dos

Before this, we could delete a to-do, but that was it. Even though our data structure was set up to have a complete attribute that could change, all we did was filter out the completed ones entirely from the data.

Here’s what that data structure is like:

{
  title: `text of to-do`,
  complete: false,
  id: self.crypto.randomUUID()
}

Now, when we check that checkbox in the UI to complete a to-do, we need to:

  1. Set complete to true if the list item is in the active list
  2. Remove the list item entirely if the to-do is already in the completed list

We’ll update our function to be called toggleTodo and do it like this:

function toggleTodo(event) {
  const listItem = event.target.parentElement;
  // Trigger complete animation
  listItem.classList.toggle("complete");
  setTimeout(() => {
    // list item is already complete, remove it
    if (listItem.dataset.complete === "true") {
      TODOs = TODOs.filter((todo) => !todo.complete);
    } else {
      // list item is just being set to complete now
      TODOs.forEach((todo) => {
        if (todo.id === listItem.id) {
          todo.complete = !todo.complete;
        }
      });
    }

    localStorage["data"] = JSON.stringify(TODOs);

    if (!document.startViewTransition) {
      buildUI();
    } else {
      document.startViewTransition(() => {
        buildUI();
      });
    }
  }, 1000);
}

This isn’t a big change from last time, just one little fork in the logic that either removes it or updates the data.

Now we need a control though to decide if we’re looking at the active to-dos or the completed ones. Let’s make that control in HTML:

<div class="todo-type-toggles">
  <button aria-pressed="true">Active</button>
  <button>Completed</button>
</div>

Now when you press the buttons, we’ll swap the state and re-build the UI accordingly:

const toggles = document.querySelectorAll(".todo-type-toggles > button");

toggles.forEach((toggle) => {
  toggle.addEventListener("click", (event) => {
    toggles.forEach((toggle) => {
      toggle.setAttribute("aria-pressed", false);
    });
    toggle.setAttribute("aria-pressed", true);

    if (toggle.textContent === states.ACTIVE) {
      buildUI(states.ACTIVE);
    } else {
      buildUI(states.COMPLETED);
    }
  });
});

Now I’m calling buildUI() with a param to declare which type I want to see. I like using little ENUM type variables for this just to make sure we don’t do typos.

const states = {
  ACTIVE: "Active",
  COMPLETED: "Completed"
};

Then we update the function to display one or the other…

function buildUI(state) {
  let HTML = ``;
  let viewTODOs = [];

  if (state === states.COMPLETED) {
    viewTODOs = TODOs.filter((todo) => todo.complete);
  } else {
    viewTODOs = TODOs.filter((todo) => !todo.complete);
  }

  if (viewTODOs.length === 0) {
    HTML = `<li class="empty">Nothing to do!</li>`;
  }

  // Loop over the viewTODOs and build HTML to insert, exactly as before.
}

This gives us an empty state as well.

And we’ve done it!

Perhaps more back-endy readers will be like “is this dude not sanitizing data before it goes to data storage?” and that would be a smart observation. We should probably be sanitizing the HTML. But for now, the only person you can pwn with this is yourself, so not a massive deal.

Here’s where we’ve gotten now:

Article Series

]]>
https://frontendmasters.com/blog/building-a-todo-app-from-scratch-step-5-extra-functionality/feed/ 0 1169
Building a TODO App from Scratch — Step 4 — Styling & Interactive Choices https://frontendmasters.com/blog/building-a-todo-app-from-scratch-step-4-styling-interactive-choices/ https://frontendmasters.com/blog/building-a-todo-app-from-scratch-step-4-styling-interactive-choices/#respond Wed, 28 Feb 2024 21:17:43 +0000 https://frontendmasters.com/blog/?p=1020 I’m already a little over all the green. What was I thinking? I’m much more of a nice gray color palette kinda guy with some fun poppy colors. That’s what we need to get to here. When we left off, we just had wireframes going. It’s time to get some style on this thing!

Article Series

Custom Properties

We can get rid of that wireframe look by removing all those 1px borders and applying some backgrounds. Let’s give ourselves some Custom Properties to work with, so we can apply colors in such a way they are easy to change later.

html {
  --gray800: oklch(10% 0% 0);
  --gray600: oklch(40% 0% 0);
  --gray100: oklch(92% 0% 0);
  --brand: oklch(85% 0.3 145);

  background: var(--gray100);
}

You can see we’re using the lightest gray right away in colorizing the background.

I’m using OKLCH color function here because I find it the generally-nicest of all the color models. One very immediate thing it does for us here is that it unlocks P3 colors. That --brand color you see there is actually a bit more bright and vibrant than is even possible in the sRGB color space (like a hex code or any of the more common color functions you may be familiar with, like rgb()).

Applying those colors to the relevant sections gets us a start:

Fonts

We don’t need to go too far down the typography rabbit hole here, as all our design calls for is the header, one input, and a list of to-do items. Keeping it simple and fast (not loading fonts is… fast), is ideal, so:

html {
  font-family: system-ui, sans-serif;
}

You’d think system-ui would be boring, but as a little fun and progressive enhancement, we can use some font-variation-settings on it in the case that the font that loads supports them. It’s not the only one, but Apple’s San Francisco font ships as a variable font, so we can do fun things like stretch it out.

header {
  background: var(--gray800);
  color: white;
  h1 {
    font-variation-settings: "wght" 900, "wdth" 700;
  }
}

I love this look, myself:

SVGs

We need SVG’s for the “check” of the logo and that we’ll use when a user checks off a to-do. I’m kinda thinking we just re-use the same one for both as that just makes good sense. Plus we’ll make a “+” SVG too. It might be tempting to find unicode characters for these, but I’m telling you, just use SVG. The predictable bounding box of SVG will be your friend (no fighting against weird space).

There are loads of resources for finding SVGs to use. I’m usually a Noun Project guy, but if we needed, say, 10 or more icons I’d probably try to find a more cohesive set somewhere. These are also so simple that could (should) draw them ourselves (even directly in code), but we’ll save that for another day.

Ultimately, we’ll just get our hands on a “check” icon and use it in the logo…

<header>
  <svg ... class="svg-check site-logo">
    <polyline ... />
  </svg>
  <h1>TODOs</h1>
</header>

… in our template literal for each to-do …

HTML += `<li id="${todo.id}">
  ${todo.title}
  <button aria-label="Complete" class="button-complete">
    <svg ... class="svg-check">
      <polyline ... />
    </svg>
  </button>
</li>`;

… and then our “+” in the form:

<form id="todo-form" class="todo-form">
  <label>
    <span class="screen-reader-text">New TODO</span>
    <input type="text" name="todo">
  </label>
  <button id="button-add-todo" class="button-add-todo">
    <svg ... class="svg-plus">
      <g>
        <line ... />
        <line ... />
      </g>
    </svg>
  </button>
</form>

There is a lot to know about SVG so I’m really just dropping the basic usage in here. I’ve chosen to drop the SVG code right “inline” with the HTML because I generally think that’s best because it’s fast and offers the most styling control. If you wanna dig more into SVG, I did write a book about it one time you could check out.

With the SVG’s I chose in there:

Adding To-Do Interactions

When we add a new to-do, I want to give the adding button a temporary class so we can do something fun with it. So we’ll use a saved reference to that button and do that:

buttonAddTodo.classList.add("added");

But this is an opportunity! We could add a different class upon a failure. What failure? Not entering any text, for one. We could make the call not to allow entering blank to-dos, which at this point I think is a decent idea.

So the form event handler then becomes like:

form.addEventListener("submit", (event) => {
  event.preventDefault();
  // Don't allow empty todo
  if (!form[0].value) {
    buttonAddTodo.classList.add("shake");
    return;
  }
  addTodo(event);
  form.reset();
});

Now check out these little @keyframe animations I can do with those classes:

.button-add-todo {
  ...

  &.shake {
    rotate: 0deg;
    transform-origin: bottom right;
    animation: shake 0.4s cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
  }
  &.added {
    transform-origin: center center;
    animation: added 0.4s cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
  }
}
@keyframes shake {
  50% {
    rotate: -12deg;
  }
}
@keyframes added {
  50% {
    rotate: 1turn;
    translate: 0 50px;
  }
}

A little fun, maybe?

Let’s be careful to remove those classes after the animation happens, so that another animation can be triggered freshly when needed:

buttonAddTodo.addEventListener("animationend", () => {
  buttonAddTodo.classList.remove("shake", "added");
});

Completing To-Do Interactions

We’ve got a big ol’ box sitting there waiting for to-do items to be checked. And now that we’re using SVG, my temptation is to do a little fun “line drawing” with that. That is, see the checkbox be “drawn” as we complete the to-do, before removing it.

Here’s the trick.

First we go find the SVG itself in our template literal, and add this special pathLength attribute to the actual shape:

<svg width="20" height="20" viewBox="0 0 241.44 259.83" class="svg-check">
  <polyline pathLength="1" points="16.17 148.63 72.17 225.63 225.17 11.63" />
</svg>

This just makes animating it much easier.

Then we set and animate some specific SVG properties to do the drawing:

.todo-list {
  ...
  li {
    ...
    .svg-check {
      opacity: 0;
    }
    &.complete {
      .svg-check {
        opacity: 1;
        stroke-dasharray: 1;
        stroke-dashoffset: 1;
        animation: do-check 1s infinite alternate;
      }
    }
  }
}

@keyframes do-check {
  from {
    stroke-dashoffset: 1;
  }
  to {
    stroke-dashoffset: 0;
  }
}

Check out the drawing effect at work:

The trick to getting the animation to run before being removed is just to … wait.

function removeTodo(event) {
  const listItem = event.target.parentElement;
  listItem.classList.toggle("complete");
  setTimeout(() => {
    TODOs = TODOs.filter((todo) => todo.id !== event.target.parentElement.id);
    localStorage["data"] = JSON.stringify(TODOs);
    buildUI();
  }, 1000);
}

You can see I’m just waiting for a second there, which is about the same, a little longer, than the animation itself. We could tie it to the animationend event here also, but for some reason it felt better to me to leave this bit of business logic in the code here rather than tying it to a CSS thing.

Really smoooooth completions

What if we remove a to-do from the middle of the list. It’ll be jerky won’t it? It certainly could be. This is one of those classic situations in CSS where being able to “animate to/from auto” would be nice. In this case, we want to animate from “however tall a list item is” to zero. That way the bottom items in the list would slide up, making a much more comfortable animation. But honestly, even animating the height here feels like slight of hand, what I actually want is just not to think very hard about it and for the list items to just slide up into place, if at all possible.

Turns out this is a lovely use-case for View Transitions. Without getting very deep at all into it, essentially all we need to do is:

  1. Give all our to-dos a unique view-transition-name
  2. When we update the DOM, e.g. buildUI(), wrap that in a startViewTransition() function.

So our template literal is updated to be like…

  TODOs.forEach((todo) => {
    HTML += `<li id="${todo.id}" style="view-transition-name: list-item-${todo.id};">

We already have a unique ID so we’ll steal that for the dual purpose here.

Then wrap our DOM updating function like:

if (!document.startViewTransition) {
  buildUI();
} else {
  document.startViewTransition(() => {
    buildUI();
  });
}

That’s the same and progressive-enhancement friendly way to do it. It’s kind of required here, as that startViewTransition will throw in browsers that don’t support it and it could break our whole site.

Look at us know:

This is friggin’ cool if you ask me.

So far

Just a little bit more functionality to go!

Article Series

]]>
https://frontendmasters.com/blog/building-a-todo-app-from-scratch-step-4-styling-interactive-choices/feed/ 0 1020
Testing Your JavaScript Knowledge Course (and Podcast Interview with Lydia Hallie) https://frontendmasters.com/blog/testing-your-javascript-knowledge/ https://frontendmasters.com/blog/testing-your-javascript-knowledge/#respond Wed, 28 Feb 2024 18:15:10 +0000 https://frontendmasters.com/blog/?p=1051 Quizzing has always been a highly requested feature, and we wanted it not just to be a test — but a course that combines challenging questions with in-depth, quality instruction. We released our first quiz-based course last year covering advanced web development topics and folks seem to really enjoy it.

Today, we’re excited to release our second quiz-based course by Lydia Hallie with questions on JavaScript!

Lydia teaching the new JavaScript quiz course

Test Your JavaScript Knowledge

Challenge your core knowledge of JavaScript with 50 interactive quiz questions covering topics like the event loop, scopes and closures, the this keyword, classes and prototypes, garbage collection, and more! After each question, you’ll get an in-depth visual explanation from Lydia.

I personally really enjoyed her visual explanations of the event loop and how JavaScript processes the microtask and macrotask queues. It’s difficult to wrap your head around asynchronous code – things like timers and async/await – and Lydia really challenged how much I thought I knew. (BTW, she recommends this classic talk on YouTube: What the heck is the event loop anyway? if you haven’t seen it or don’t know much about the event loop, go check it out)

Lydia visually explaining the JavaScript event loop

Other highlights of the course include:

  • Solidify your understanding of asynchronous programming and JavaScript’s event loop
  • Grasp the “this” keyword and the nuances of scope and closures
  • Test your knowledge of module loading, maps/sets, proxies, and template literals
  • Utilize classes and prototypes for prototypal inheritance
  • Explore modules and garbage collection for code optimization

We hope you enjoy Testing Your JavaScript Knowledge!

Podcast Interview with Lydia Hallie

We also did a podcast interview with Lydia and put it up on YouTube: From Self Taught Coder to Vercel Through Open Source | Frontend Masters Podcast Ep.12

Lydia Hallie takes us through her tech journey, from early coding experiments to becoming a prominent figure in the developer community as a Staff Developer Relations Engineer for Vercel. Lydia shares her unique approach to tech talks, emphasizing authenticity and leveraging visual storytelling to make complex concepts accessible. She discusses the challenges of public speaking, her collaboration with Addy Osmani on patterns.dev, and her strategies for engaging with her audience beyond conventional presentation styles. Lydia also opens up about her personal struggles with burnout, highlighting the critical need for balance between work intensity and self-care. Her story is not just about her technical achievements but also about her insights into maintaining passion and productivity in tech. Lydia’s reflections offer valuable lessons on the importance of self-awareness and the courage to prioritize well-being alongside professional growth.

The episode is up on Spotify and Apple Podcasts as well!

]]>
https://frontendmasters.com/blog/testing-your-javascript-knowledge/feed/ 0 1051
JSR https://frontendmasters.com/blog/jsr/ https://frontendmasters.com/blog/jsr/#respond Fri, 23 Feb 2024 16:33:12 +0000 https://frontendmasters.com/blog/?p=1010 Deno is getting in on the package registry business with JSR (“JavaScript Registry”). The name should help people understand it’s not Deno-specific, but instead built to support the ever-growing number of JavaScript runtimes. There isn’t much public information (it’s waitlisted), but David Bushell was able to take it for a spin.

]]>
https://frontendmasters.com/blog/jsr/feed/ 0 1010
Building a TODO App from Scratch — Step 3 — Basic JavaScript Functionality https://frontendmasters.com/blog/building-a-todo-app-from-scratch-step-3-basic-javascript-functionality/ https://frontendmasters.com/blog/building-a-todo-app-from-scratch-step-3-basic-javascript-functionality/#respond Thu, 22 Feb 2024 16:39:19 +0000 https://frontendmasters.com/blog/?p=903 We left off with a wireframe-y looking HTML-ized version of our design. Personally, I’m a little tempted to get some CSS on there, but let’s hold off and do the very basic JavaScript functionality first. That will make it, ya know, actually functional, and having some of the interactive stuff in place might inform some of our styling choices anyway.

Article Series

Where is the data going to go?

There are more ways and places to save data on the web than you can shake a stick at. Much like we slowed down at the beginning and did some design thinking at first, we might do well do do a little data thinking and consider the experience we’re trying to build.

For example, we might want to offer our TODO app for multiple users who log in to the website. If that’s the case, we’ll need to store the data in such a way that each user has their own set of to-dos associated with them. That would be fun! In that case we’d have to think about security and ensuring correct auth around data access. Maybe a bit much for a first crack though, so let’s table that for now.

Circling back to the front-end, it would be nice to get our data there ultimately as JSON. JSON is just so browser-friendly, as JavaScript can read it and loop over it and do stuff with it so easily. Not to mention built-in APIs.

That doesn’t mean we have to store data as JSON, but it’s not a terrible idea. We could use a more traditional database like MySQL, but then we’d need some kind of tool to help us return our data as JSON, like any decent ORM should be able to help with. SQLlite might be even better.

Or if we went with Postgres, it supports a better native json field type, and built-in utility functions like ROW_TO_JSON().

Still, just keeping the data as JSON sounds appealing. I’m not a huge data expert, but as I understand it there are JSON-first databases like MongoDB or CouchDB that might make perfect sense here. I’ve used Firebase a number of times before and their data storage is very JSON-like.

There are a million data storage options, and every data storage company wants your data. Just the way it is, kid.

Overwhelmed? Sorry. Let’s make like Liz Lemon and not overthink it. Let’s assume we’re going to use JSON data, and dunk that data as a string in localStorage. While this isn’t exactly a database, we can still nicely format our data, make it extensible, and pretend like we’re interfacing with a fancier database system.

What we’re giving up with localStorage is a user being able to access their data from anywhere. If they so much as open our TODO app in another browser, their data isn’t there. Let alone open the site on their phone or the like. Or do something blasphemous like clear their browser data. So it’s rudimentary, but it’s still “real” enough data storage for now.

What is the data going to look like?

JSON can be an {} Object or [] Array. I’m thinking Array here, because arrays have this natural sense of order. We talked about being able to re-arrange to-do items at some point, and the position in the array could be all we need for that.

As best I know, as of ES2020, I believe Objects maintain order when you iterate over them in all the various ways. But I don’t think there is any reasonable way to change that order easily, without creating a brand new Object and re-inserting things in the newly desired order. Or, we’d have to keep an order value for each item, then update that as needed and always iterate based on that value. Sounds like too much work.

So…

[
  { /* to-do item */ },
  { /* to-do item */ }
]

(Astute readers will note that JSON doesn’t actually have comments, unless we fix it somehow.)

If we were supporting a multi-user system, perhaps all the data would be an object with each user ID being a key and the data being an array like above. One way or another, one use per Array.

Now what does each item look like? Based on our design, we really only need a few things:

  1. Title (text of the to-do)
  2. Complete (whether it is done or not)

We could decide that we don’t need “complete” because we’ll just delete it. But any to-do app worth it’s salt will be able to show you a list of completed items, and you can always delete them from there.

We talked about using the array order for the visual order of the to-dos. I think I’m fine with that for now, knowing we can always add ordering properties if we really feel like it. In fact, we can add whatever. We could add dates like a “created at”, “modified at”, “due date”, or “completed at” if we felt it would be useful. We could add tags. We could add a description. But these kind of things should be design and UX driven, as we well know by now. Don’t just go adding data speculatively, that tends to not end well.

When it comes to updating/deleting existing to-dos, we’re going to need a way to update just that item in the data. That’s why I brought up the array order again, because theoretically we could know which item we’re dealing with by the DOM order, then match that to the array order to find the right item. But something about that feels janky to me. I’d rather have a more solid-feeling one-to-one connection between the UI and the data. Maybe it’s just me, but it feels better. So let’s add a unique identifier to each todo.

So our now-list-of-three will look like this in the data:

[
  {
     title: "Walk the dog", // string
     completed: false,      // boolean
     id: "something-unique" // string
  },
  {
    // more! 
  }
]

Seems workable and extensible to me!

Writing data

There is one way to add a new item on our site: submitting the form. So if we get ahold of that form in JavaScript and watch for the submit event, we’re in business.

const form = document.querySelector("#todo-form");

form.addEventListener("submit", (event) => {
  event.preventDefault();

  // Add to-do to data and render UI

  form.reset();
});

Two little tricks there.

  1. The preventDefault is because we’re handling the submission in JavaScript so we’re preventing the browser from trying to perform the default action of going to a new page.
  2. The reset bit is a nice little built-in UI for resetting the fields, so we don’t have to manually clear them ourselves. After we add a to-do, we don’t want that same text just sitting there in the input, it should go back to blank.

We talked about all our data being one big JSON-able Array. So:

let TODOs = [];

Then we can push into that array with the new data. We know the complete value will be false (we just added it!) and the title will be from the input.

TODOs.push({
  title: event.target[0].value,
  complete: false,
  id: self.crypto.randomUUID()
});

That last bit is the browser giving us a unique identifier for free! We could used the package for them, but we just don’t need to anymore. UUID’s are cool. There is a practically-zero chance of ever getting a duplicate ever. Wikipedia:

… only after generating 1 billion UUIDs every second for approximately 100 years would the probability of creating a single duplicate reach 50%.

We’ve decided we’re just going to keep the data in localStorage for now, so after we’ve updated our TODOs Array, let’s dump it there.

localStorage["data"] = JSON.stringify(TODOs);

Uh, that was easy.

Now that we’ve added the data, we know we’ll need to re-render the UI. But we’ll make a function for that in the next section, as obviously we’ll need to render the UI when we read the data when the page loads as well.

Reading data

Getting the data out of localStorage is just as easy as writing to it: localStorage["data"]. That’ll have our JSON data in it. Probably. If we’ve written to it before. Just to be sure, let’s check before we parse out the data.

let TODOs = [];

if (localStorage["data"] !== null && localStorage["data"] !== undefined) {
  TODOs = JSON.parse(localStorage["data"]);
}

Just doing that once when the page is loaded will ensure our TODOs variable is loaded with what we got.

Now we need to render the UI. We already figured out we need to do this in several situations:

  1. When the page loads
  2. When we add a new to-do
  3. When we complete a to-do

So let’s write it as a function so we can call it in all those situations.

const list = document.querySelector("#todo-list");

function buildUI() {
  let HTML = ``;
  TODOs.forEach((todo) => {
    HTML += `
      <li id="${todo.id}">
       ${todo.title}
       <button aria-label="Complete" class="button-complete">
         <svg class="svg-check"><path d="..." /></svg>
       </button>
      </li>`;
  });
  list.innerHTML = HTML;
}

We knows TODOs is an Array, so we loop over it, creating one big string of HTML with all the <li>s we’ll populate the <ol> with. (We’ll monkey with that SVG later.)

I feel like the native JavaScript Template Literal is a good fit here. That’s the string within backticks (`). This allows us to write multi-line strings and interpolate variables inside. This is a place where there is lots of choice though! We could have used a native HTML <template> here, and perhaps we will in the future. We could have used a Handlebars template or the like. If you’re used to using a JavaScript framework, this is essentially a component and essentially the heart of whatever framework it is. Like the JSX of React.

The downsides of a Template Literal is that you don’t get syntax highlighting usually. It’s not going to be linted or checked like your other HTML. Still, I like how simple the Template Literal is here, let’s keep it.

Completing a to-do

In the HTML for each of our to-dos, remember we have a <button> designed for clicking to complete a to-do. But they don’t have click event handlers on them yet. We could put an onclick handler as an attribute right on them. That’s not the world’s worst idea, since they would automatically have interactivity applied to them the second they hit the DOM.

Just go another way though: event delegation. We can just watch for clicks on the whole document, and if the event originated on that kind of button, then we can do our work.

document.documentElement.addEventListener("click", (event) => {
  if (event.target.classList.contains("button-complete")) {
    // Click happened on a Complete button
  }
});

There is a little gotcha here though! We have an <svg> in our button, and it’s possible/likely the user clicks directly on that, so event.target will be that and not the <button>. So a smidge of CSS will help us:

.svg-check {
  display: block; /* prevent weird line-height issue */
  pointer-events: none; /* stop click events from being target */
}

Now we need to do the actual work. We can figure out exactly which to-do this is by the unique ID that we gave the <li> element. That will match the ID in our data. So we look through our data and find that ID, mark it as complete, and put the data back.

document.documentElement.addEventListener("click", (event) => {
  if (event.target.classList.contains("button-complete")) {
    TODOs = TODOs.filter((todo) => todo.id !== event.target.parentElement.id);
    localStorage["data"] = JSON.stringify(TODOs);
    buildUI();
  }
});

That filter function is instantly removing the to-do with a matching ID from the data. Ultimately our plan is to update the complete value in our data, so that we can show a list of completed to-dos. But we’ve done a lot today already, so let’s revisit that when we do more with JavaScript. We’ve still got editing to do and such.

This is a little akwardness of the localStorage setup we have. Every time we touch our data, we rewrite the entire set of data each time. When you’re working with a “real” database, don’t download the entire database and replace the entire database when small changes are made, that would just be silly. But our data is so small/light here, even if there were a few hundred to-dos, it’s not that big of a deal. But certainly a real database is a cleaner and more scalable approach. We could have also architected things differently, making each to-do a unique key in localStorage, but that didn’t have the Array characteristic we wanted, and just feels kinda sloppy to me.

See that we’re calling our buildUI() function after making the data change as well, ensuring our UI is in sync with our data.

So Far

Article Series

]]>
https://frontendmasters.com/blog/building-a-todo-app-from-scratch-step-3-basic-javascript-functionality/feed/ 0 903
Syntax Highlighting with no spans?! https://frontendmasters.com/blog/syntax-highlighting-with-no-spans/ https://frontendmasters.com/blog/syntax-highlighting-with-no-spans/#respond Wed, 21 Feb 2024 19:50:05 +0000 https://frontendmasters.com/blog/?p=950 When I think of showing off syntax highlighted code on the web, I think of code that takes in a string of code, finds relevant substrings (tokens) with elaborate RegExes, then wraps them in <span>s with classes to that color can be applied to those classes. This is quite literally how it works. The really nice syntax highlighter Prism has all sorts of RegExes to find the right stuff. I think if you poked around other syntax highlighting tools for the web you’d find largely the same approach.

BUT THERE IS ANOTHER WAY. Bramus digs into it nicely here.

Brass tacks, there is a Highlight() API in JavaScript, where you can set a Range() on text, then give it a name. Then in CSS, you can select that highlight with ::highlight(name) and colorize it. This is useful for simple use cases like building on-page search yourself, but is everything you need for even something as elaborate as syntax highlighting.

I would have guessed this would be a real boon for performance, with so much less HTML required and really no DOM manipulation at all. But as Bramus points out, the performance actually have some problems. Would be nice to see some actual benchmarks. There are variety of other downsides too, like being quite limited in styling (like ::selection) and not having pointer events to do things like hover styles or positioned popups.

To me it seems like the biggest downside is that you could never do this server-side, which is almost certainly a performance win, whereas you can do the make-a-bunch-of-spans style highlighting server-side.

]]>
https://frontendmasters.com/blog/syntax-highlighting-with-no-spans/feed/ 0 950