Adding Aside Content to CKEditor 5 in Drupal: A JavaScript Solution Part 2

Published Jul 28, 2023
Updated Jul 29, 2023
By Simon

This is part 2 and the last part of adding aside elements to content created using CKEditor 5. Since at the time of writing, there wasn't a widget to insert aside content I decided to go the quick hacking route and use JavaScript to convert elements that have been styled in CKEditor. If you want more information on the set up I suggest you read part 1 where we also create the aside element and add the blockquote element as a child.

In this part, we will be removing the unneeded wrapper element used in CKEditor. In this case, we used a blockquote and the resulting HTML markup from part 1 is shown below. 

<aside>
    <blockquote class="hint">
        <h2>
            Heading of a hint aside
        </h2>
        <p>
            Something semi-related to the main content, an aside.
        </p>
    </blockquote>
</aside>

What we use
querySelectorAll() instance method
while statement
forEach() array method
DocumentFragment() constructor
Plus other Javascript methods and properties.

Remove the blockquote element but not its children using a JavaScipt DocumentFragment

To remove an element but not the children of an element we can use the createDocumentFragment() method. We can use fragments to save segments of the document structure. Fragments are not part of the main DOM tree (active document tree structure.)

const hint = document.querySelector('blockquote.hint');
const asideNode = document.createElement("aside");
hint.parentNode.insertBefore(asideNode, hint);
asideNode.appendChild(hint);

Continuing on from what we already have from Part 1 as shown above, follow these steps: 

  1. First, create a new constant and assign a new fragment to it. We can use the DocumentFragment constructor to do this.
const fragment = new DocumentFragment();
  1. Then we can use a while statement to loop through all the elements inside out hint element and add them to the fragment. 
while (hint.firstChild) {
  fragment.appendChild(hint.firstChild);
}
  1. Next, we add the fragment to the aside element using the appendChild method
asideNode.appendChild(fragment);
  1. Then remove the hint element. 
hint.remove();
  1. Finally, you'll need to add a class to the aside element so you can style it.
asideNode.className = 'hints';

Then in your CSS, you'll need to add a rule to style the aside element. The example CSS is from the styles we added to the blockquote element when setting up the styles to use in CKEditor 5.

aside.hints {
  padding: 1rem;
  color: #107A0E;
  background-color: #F5FAF2;
  border: 1px solid #B6DEA5;
  border-left: 5px solid #B6DEA5;
  font-style: normal;
}

That's it, we have now removed the blockquote element from the DOM tree but not its children. Your code should look like this below.

<aside>
    <h2>
        Heading of a hint aside
    </h2>
    <p>
        Something semi-related to the main content, an aside.
    </p>
</aside>

The final thing you will most probably want to do is apply the same process to all the elements that have the same class applied. To do this we use querySelectorAll and a forEach loop. 

Using querySelectorAll & forEach to target and convert all elements of the same class

With the code from the last section, we will make a few changes to convert all elements with the class hint. 
The first change we will make is the querySelector method. Change this to querySelectorAll

-const hint = document.querySelector('blockquote.hint');
+const hint = document.querySelectorAll('blockquote.hint','p.hint'); 

By using querySelectorAll we get an array of elements which we have stored in the constant hint. You will notice that I have added the paragraph hints here too. This is more for demonstration that you can add multiple selectors separated by a comma. Please read the following note.

We then need to loop through each element and apply the same changes. To do this we use the forEach loop. Below is the code showing the differences from our single-element conversion above.

hint.forEach((b) => {
  const asideNode = document.createElement("aside");
  b.parentNode.insertBefore(asideNode, b);
  asideNode.appendChild(b);
  
  const fragment = new DocumentFragment();
  while (b.firstChild) {
    fragment.appendChild(b.firstChild);
  }
  asideNode.appendChild(fragment);
  b.remove();
  asideNode.className = 'hints';
}
  1. The first thing we have done is wrap our code block in a forEach loop and passed in each value of our hint array as an argument. In this case, our argument is b.
  2. Then, we switched out all occurrences of the reference to the querySelector value, which is hint, for the argument which is passed in.

That's it. Now all our elements on the page with the class hint are wrapped in an aside element with the original element removed. 

Thoughts about aside elements and document structure

You will notice that now the paragraph tag that had the hint class is removed. Even though I can't find anywhere that explicitly states this is wrong, in the specification it does say "The aside element represents a section of a page that consists of content...". In reading this I do get the feeling that it is best to have structure inside the aside so in the case we have just worked on I would not include the p.hint in the querySelectorAll method.

Also, taking the above case into consideration sometimes you may want to have a blockquote inside the aside. If that is the case then you would need to set up these types of blockquotes with a different class so they weren't included. 

That's it for this small snippet. We have learnt how you can change the parent element type using JavaScript. In this case, we replace the blockquote with an aside to make it semantically correct but you could use this snippet to change any parent element. 

For me, this was a good refresher and I am using this on production until I get time to build an aside widget for CKEditor 5. It's a great quick solution that allowed me to use something I am comfortable working with, without the need to learn something new.

If you enjoyed this and would like to learn more about front-end development and design be sure to sign up for the newsletter below. 

Thanks for reading! 
 

Tags

Add new comment

The content of this field is kept private and will not be shown publicly.

Plain text

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.
Please leave questions and constructive comments, I welcome them.
CAPTCHA
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.