Tag: Behind

The Thought Process Behind a Flexbox Layout

I just need to put two boxes side-by-side and I hear flexbox is good at stuff like that.

Just adding display: flex; to the parent element lays out the children in a row.

Well, that’s cool. I guess I could have floated them, but this is easier.

They should probably take up the full space they have though. Can I just stretch the parent to 100% wide? Well, I can, but that’s apparently not going to affect the child elements.

If the parent element has more space than the children need, it doesn’t do anything to help the children fill the space alone.

Maybe I can use width: 50%; on the children? That works, but I thought the point of flexbox is that you don’t have to be all specific about width. Ah yes, flexbox has all of these ways of expressing the growy-shrinky-initial behavior of children. Looks like flex: 1; is about as easy as it gets here.

Applying flex: 1; to the children allow them to grow and fill the space.

I like how I haven’t had to do any math or hard code values so far. Can I make it a three-up pattern without touching CSS?

Nice.

Hmmm, wait. The sizing is a bit, uhhhh, flexy? Nothing is forcing these boxes to be one-third of the container all the time.

Looks like flex-basis: 33% doesn’t fix this. flex: 0 0 33%; also doesn’t do the trick. Looks like width: 33%; flex-shrink: 0; does though, if we’re really wanting to strongarm it.

Sometimes a design calls for exactly equal size boxes. This is maybe CSS Grid territory, but whatever.

The long word forcing that sizing behavior at narrow widths is perhaps an uncommon scenario. We could have also solved it with word-break: break-word; hyphens: auto; on the child.

Another thing we could do it just let the dang items wrap instead of being so strict about it. Flexbox can do that, right?

Oh hey, that reminds me how cool it is that those first two items are the same height. That’s the default stretch behavior, but it can be controlled as well. And it’s by row, which is why the second row has its own different height.

What if I want that first “Love” block to be on top instead? Looks like I can re-order it, eh? Let’s see, the default order is 0, so to be first, I do order: -1;.

Ha! That kinda worked. But I meant that I want it to take up the full width on the top row. I guess I can just kick it up to flex-basis: 100%; and the others will wrap accordingly.

It’s pretty weird to have the source order and visual order different like this. Maybe that should go in the “don’t ever do this” category.

What if I wanna bail on the whole flexbox thing at a certain point? Part of me wants to change the direction to go vertical with flex-direction: column; and force the children to be full-width. Ah, a simple display: block; on the parent does that in one swoop.

Rather than changing all the flexbox stuff to handle a column layout, we just turn flexbox off.

Flexbox can do way more stuff! One of my favorites is how auto margins work to “push” other elements away when there is space. This is just one little journey playing with some UI and seeing the useful things flexible does along with things that can make it confusing.

The post The Thought Process Behind a Flexbox Layout appeared first on CSS-Tricks.

CSS-Tricks

, , , ,

The Thinking Behind Simplifying Event Handlers

Events are used to respond when a user clicks somewhere, focuses on a link with their keyboard, and changes the text in a form. When I first started learning JavaScript, I wrote complicated event listeners. More recently, I’ve learned how to reduce both the amount of code I write and the number of listeners I need.

Let’s start with a simple example: a few draggable boxes. We want to show the user which colored box they dragged.

<section>   <div id="red" draggable="true">     <span>R</span>   </div>   <div id="yellow" draggable="true">     <span>Y</span>   </div>   <div id="green" draggable="true">     <span>G</span>   </div> </section>  <p id="dragged">Drag a box</p>

See the Pen
Dragstart events
by Tiger Oakes (@NotWoods)
on CodePen.

The intuitive way to do it

I wrote separate event listener functions for each element when I first started learning about JavaScript events. It’s a common pattern because it’s the simplest way to start. We want specific behavior for each element, so we can use specific code for each.

document.querySelector('#red').addEventListener('dragstart', evt => {   document.querySelector('#dragged').textContent = 'Dragged red'; });  document.querySelector('#yellow').addEventListener('dragstart', evt => {   document.querySelector('#dragged').textContent = 'Dragged yellow'; });  document.querySelector('#green').addEventListener('dragstart', evt => {   document.querySelector('#dragged').textContent = 'Dragged green'; });

Reducing duplicate code

The event listeners in that example are all very similar: each function displays some text. This duplicate code can be collapsed into a helper function.

function preview(color) {   document.querySelector('#dragged').textContent = `Dragged $  {color}`; }  document   .querySelector('#red')   .addEventListener('dragstart', evt => preview('red')); document   .querySelector('#yellow')   .addEventListener('dragstart', evt => preview('yellow')); document   .querySelector('#green')   .addEventListener('dragstart', evt => preview('green'));

This is much cleaner, but it still requires multiple functions and event listeners.

Taking advantage of the Event object

The Event object is the key to simplifying listeners. When an event listener is called, it also sends an Event object as the first argument. This object has some data to describe the event that occurred, such as the time the event happened. To simplify our code, we can use the evt.currentTarget property where currentTarget refers to the element that the event listener is attached to. In our example, it will be one of the three colored boxes.

const preview = evt => {   const color = evt.currentTarget.id;   document.querySelector('#dragged').textContent = `Dragged $  {color}`; };  document.querySelector('#red').addEventListener('dragstart', preview); document.querySelector('#yellow').addEventListener('dragstart', preview); document.querySelector('#green').addEventListener('dragstart', preview);

Now there is only one function instead of four. We can re-use the exact same function as an event listener and evt.currentTarget.href will have a different value depending on the element that fires the event.

Using bubbling

One final change is to reduce the number of lines in our code. Rather than attaching an event listener to each box, we can attach a single event listener to the <section> element that contains all the colored boxes.

An event starts off at the element where the event originated (one of the boxes) when it is fired. However, it won’t stop there. The browser goes to each parent of that element, calling any event listeners on them This will continue until the root of the document is reached (the <body> tag in HTML). This process is called “bubbling” because the event rises through the document tree like a bubble.

Attaching an event listener to the section will cause the focus event to bubble from the colored box that was dragged up to the parent element. We can also take advantage of the evt.target property, which contains the element that fired the event (one of the boxes) rather than the element that the event listener is attached to (the <section> element).

const preview = evt => {   const color = evt.target.id;   document.querySelector('#dragged').textContent = `Dragged $  {color}`; };  document.querySelector('section').addEventListener('dragstart', preview);

Now we’ve reduced many event listeners to just one! With more complicated code, the effect will be greater. By utilizing the Event object and bubbling, we can tame JavaScript events and simplify code for event handlers.

What about click events?

evt.target works great with events like dragstart and change, where there are only a small number of elements that can receive focus or have input changed.

However, we usually want to listen for click events so we can respond to a user clicking on a button in an application. click events fire for any element in the document, from large divs to small spans.

Let’s take our draggable color boxes and make them clickable instead.

<section>   <div id="red" draggable="true">     <span>R</span>   </div>   <div id="yellow" draggable="true">     <span>Y</span>   </div>   <div id="green" draggable="true">     <span>G</span>   </div> </section>  <p id="clicked">Clicked a box</p>
const preview = evt => {   const color = evt.target.id;   document.querySelector('#clicked').textContent = `Clicked $  {color}`; };  document.querySelector('section').addEventListener('click', preview);

See the Pen
Click events: Not quite working
by Tiger Oakes (@NotWoods)
on CodePen.

When testing this code, notice that sometimes nothing is appended to “Clicked” instead of when clicking on a box. The reason that it doesn’t work is that each box contains a <span> element that can be clicked instead of the draggable <div> element. Since the spans don’t have a set ID, the evt.target.id property is an empty string.

We only care about the colored boxes in our code. If we click somewhere inside a box, we need to find the parent box element. We can use element.closest() to find the parent closest to the clicked element.

const preview = evt => {   const element = evt.target.closest('div[draggable]');   if (element != null) {     const color = element.id;     document.querySelector('#clicked').textContent = `Clicked $  {color}`;   } };

See the Pen
Click events: Using .closest
by Tiger Oakes (@NotWoods)
on CodePen.

Now we can use a single listener for click events! If element.closest() returns null, that means the user clicked somewhere outside of a colored box and we should ignore the event.

More examples

Here are some additional examples to demonstrate how to take advantage of a single event listener.

Lists

A common pattern is to have a list of items that can be interacted with, where new items are inserted dynamically with JavaScript. If we have event listeners attached to each item, then y\our code has to deal with event listeners every time a new element is generated.

<div id="buttons-container"></div> <button id="add">Add new button</button>
let buttonCounter = 0; document.querySelector('#add').addEventListener('click', evt => {   const newButton = document.createElement('button');   newButton.dataset.number = buttonCounter;      // Make a new event listener every time "Add new button" is clicked   newButton.addEventListener('click', evt => {      // When clicked, log the clicked button's number.     document.querySelector('#clicked').textContent = `Clicked button #$  {newButton.textContent}`;   });    buttonCounter++;    const container = document.querySelector('#buttons-container');   container.appendChild(newButton); });

See the Pen
Lists: no bubbling
by Tiger Oakes (@NotWoods)
on CodePen.

By taking advantage of bubbling, we can have a single event listener on the container. If we create many elements in the app, this reduces the number of listeners from n to two.

let buttonCounter = 0; const container = document.querySelector('#buttons-container'); document.querySelector('#add').addEventListener('click', evt => {   const newButton = document.createElement('button');   newButton.dataset.number = buttonCounter;   buttonCounter++;    container.appendChild(newButton); }); container.addEventListener('click', evt => {   const clickedButton = evt.target.closest('button');   if (clickedButton != null) {     // When clicked, log the clicked button's number.     document.querySelector('#clicked').textContent = `Clicked button #$  {clickedButton.dataset.number}`;   } });

Forms

Perhaps there’s a form with lots of inputs, and we want to collect all the user responses into a single object.

<form>   <label>Name: <input name="name" type="text"/></label>   <label>Email: <input name="email" type="email"/></label>   <label>Password: <input name="password" type="password"/></label> </form> <p id="preview"></p>
let responses = {   name: '',   email: '',   password: '' };  document   .querySelector('input[name="name"]')   .addEventListener('change', evt => {     const inputElement = document.querySelector('input[name="name"]');     responses.name = inputElement.value;     document.querySelector('#preview').textContent = JSON.stringify(responses);   }); document   .querySelector('input[name="email"]')   .addEventListener('change', evt => {     const inputElement = document.querySelector('input[name="email"]');     responses.email = inputElement.value;     document.querySelector('#preview').textContent = JSON.stringify(responses);   }); document   .querySelector('input[name="password"]')   .addEventListener('change', evt => {     const inputElement = document.querySelector('input[name="password"]');     responses.password = inputElement.value;     document.querySelector('#preview').textContent = JSON.stringify(responses);   });

See the Pen
Forms: no bubbling
by Tiger Oakes (@NotWoods)
on CodePen.

Let’s switch to a single listener on the parent <form> element instead.

let responses = {   name: '',   email: '',   password: '' };  document.querySelector('form').addEventListener('change', evt => {   responses[evt.target.name] = evt.target.value;   document.querySelector('#preview').textContent = JSON.stringify(responses); });

Conclusion

Now we know how to take advantage of event bubbling and the event object to simplify complex jumbles of event handlers into just a few… and sometimes down to just one! Hopefully this article has helped you think about your event handlers in a new light. I know this was a revelation to me after I’d spent my early development years writing duplicative code to accomplish the same thing.

The post The Thinking Behind Simplifying Event Handlers appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]