Let's understand how to wrap & unwrap HTML elements
Tags: vanillajs, javascript, beginners, webdev
Published: 2021-11-08 15:34:42

Ever have a tag or DOM Node in Javascript and go "hmm.. I just really need that to like be in a `<span>` now...". Me too. We "wrap" and "unwrap" tags a lot in our projects so I decided to write three simple utilities that can help with that.

## wrap

```js
// wrap an element with another; super basic but makes it consistent across our apps
function wrap(el, wrapper) {
  if (el && el.parentNode) {
    el.parentNode.insertBefore(wrapper, el);
    wrapper.appendChild(el);
  }
}
```
### How it works
`wrap()` works by taking in a DOM node as `el`, the thing you want to wrap as well as a `wrapper` element. Think of the wrapper element as a `<strong>`, `<span>`, `<div>` or whatever you need to wrap something in. Then think of the `el` tag as the thing that you need to put inside that `<strong>` tag.

The function verifies that this is an element and that it has a [parentNode](https://developer.mozilla.org/en-US/docs/Web/API/Node/parentNode). In JS we need to know that the element we're about to do things to has a parent, otherwise we don't know where to place something. Because the DOM is a tree structure, we can't interact with a child's placement in the page unless we know who it's parent is.

As a sentence we get code spoken like this: "Take the element to wrap, go to it's parent, then [insert the wrapper juuuust before the element](https://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore)." This means that for a split instant we have

```html
<parent-tag>
  <wrapper-tag></wrapper-tag>
  <tag-we-want-to-wrap></tag-we-want-to-wrap>
</parent-tag>
```

Lastly, we take the tag we need to wrap and run `appendChild` to [insert it into the wrapper tag](https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild). This gives us the expected result of

```html
<parent-tag>
  <wrapper-tag>
    <tag-we-want-to-wrap></tag-we-want-to-wrap>
  </wrapper-tag>
</parent-tag>
```

## wrapAll

```js
/**
 * Wrap an array of items all at once
 */
function wrapAll(ary, wrapper) {
  if (ary && ary.length) {
    ary[0].parentNode.insertBefore(wrapper, ary[0]);
    for (var i in ary) {
      wrapper.appendChild(ary[i]);
    }
  }
}
```
### How it works
The wrapAll utility takes an array of items and wraps all of them using a methodology expressed in the `wrap()` method above. It has a simple check that we have an actual array with values and then runs through them. Then it takes the 1st item who's parent will get our wrapper inserted like before. The difference is we'll go from this:
```html
<parent-tag>
  <wrapper-tag></wrapper-tag>
  <tag-we-want-to-wrap></tag-we-want-to-wrap>
  <tag-we-want-to-wrap></tag-we-want-to-wrap>
  <tag-we-want-to-wrap></tag-we-want-to-wrap>
</parent-tag>
```
To this..
```html
<parent-tag>
  <wrapper-tag>
    <tag-we-want-to-wrap></tag-we-want-to-wrap>
    <tag-we-want-to-wrap></tag-we-want-to-wrap>
    <tag-we-want-to-wrap></tag-we-want-to-wrap>
  </wrapper-tag>
</parent-tag>
```

## unwrap

```js
// unwrap away from an element; super basic but makes it consistent across our apps
function unwrap(el) {
  if (el && el.parentNode) {
    // move all children out of the element
    while (el.firstChild) {
      el.parentNode.insertBefore(el.firstChild, el);
    }
    // remove the empty element
    el.remove();
  }
}
```
### How it works
If we can wrap then we should be able to unwrap items. For this we take in an element, verify that it has a parent just like before. Then we have to look at ALL the children in this element and act upon them as it could be that we're unwrapping more than one thing. Here's what it looks like before the run it against `wrapper-tag`:
```html
<parent-tag>
  <wrapper-tag>
    <tag-we-want-to-wrap></tag-we-want-to-wrap>
    <tag-we-want-to-wrap></tag-we-want-to-wrap>
    <tag-we-want-to-wrap></tag-we-want-to-wrap>
  </wrapper-tag>
</parent-tag>
```
We step into the wrapper tag, target the `firstChild` and insert it BEFORE the wrapper. As we go through and append to another item, the value of the [firstChild](https://developer.mozilla.org/en-US/docs/Web/API/Node/firstChild) attribute will change to be a reference to the 1st node in the wrapper. That means that in the `while` loop we'll have an array that keeps removing items from it and appending just before it like so:

#### run 1
```html
<parent-tag>
  <tag-we-want-to-wrap></tag-we-want-to-wrap>
  <wrapper-tag>
    <tag-we-want-to-wrap></tag-we-want-to-wrap>
    <tag-we-want-to-wrap></tag-we-want-to-wrap>
  </wrapper-tag>
</parent-tag>
```

#### run 2
```html
<parent-tag>
  <tag-we-want-to-wrap></tag-we-want-to-wrap>
  <tag-we-want-to-wrap></tag-we-want-to-wrap>
  <wrapper-tag>
    <tag-we-want-to-wrap></tag-we-want-to-wrap>
  </wrapper-tag>
</parent-tag>
```

#### run 3
```html
<parent-tag>
  <tag-we-want-to-wrap></tag-we-want-to-wrap>
  <tag-we-want-to-wrap></tag-we-want-to-wrap>
  <tag-we-want-to-wrap></tag-we-want-to-wrap>
  <wrapper-tag></wrapper-tag>
</parent-tag>
```
The last part is to call `el.remove()` which is a [built in method](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) on all DOM Nodes to self-delete. This effectively deletes the wrapper after we've safely moved everything out of the tag and placed prior to it.