Placing paragraphs in random order with JavaScript

I rarely work with JS and when I do, I struggle to remember much! Anyhow, I’m trying to write a script that will reorder all the paragraphs inside an <article> element randomly. It’s easy enough to find scripts online to reorder elements of an array, but the array is usually just part of the script for demo purposes and the result is output to the console, so I need to build on them.

I have a lot of pages like this:

<article>
  <p>one</p>
  <p>two</p>
  <p>three</p>
  <p>four</p>
  <p>five</p>
  <p>six</p>
</article>

That’s all there will ever be on the page: an <article> and some short paragraphs (possibly up to 100 of them).

Rather than jumping into the code, I’m first trying to work out what steps need to be taken (to plan ahead and clarify what JS needs to do).

I was assuming the first step is to create an array of the paragraphs, but I’m starting to doubt that! If I do something like this:

let paraArray = getElementsByTagName("p");

I seem to get just a list of <p> tags without their content. Damn! Do I have to get the contents of the paragraphs separately? I was hoping that wouldn’t be necessary.

If so, I’m wondering if the script will need to do something like this:

  • create an array of the contents of each paragraph
  • reorder the array
  • place all those array items back in the DOM with <p> tags wrapped around them.

Can anyone point me in the right direction for how to approach this?

Hi Ralph,

When you use getElementsByTagName you get a NodeList of paragraph elements, including their contents. You don’t need to separately fetch the text inside each paragraph.

Here’s what I would do:

  1. Select all the paragraphs
  2. Convert the NodeList to Array using Array.from().
  3. Shuffle the array using a shuffling algorithm you can find online
  4. Clear the current content of the <article> andre- append the shuffled paragraphs back to it.

Happy to help with any of that.

2 Likes

Pretty much, what I have just done here :slight_smile:

/* 
Fisher-Yates algorithm grabbed from here
https://www.freecodecamp.org/news/how-to-shuffle-an-array-of-items-using-javascript-or-typescript/
*/

const shuffle = (arr) => { 
  for (let i = arr.length - 1; i > 0; i--) { 
    const j = Math.floor(Math.random() * (i + 1)); 
    [arr[i], arr[j]] = [arr[j], arr[i]]; 
  } 
  return arr; 
};

const article = document.querySelector('article')

shuffle(Array.from(document.querySelectorAll('p')))
    .forEach((elem) => article.appendChild(elem))

Edit: I had removed this post, because I thought I had missed the brief posting a finished solution (of sorts) rather than a guide.

Here is a very unconventional way of doing it:

4 Likes

Thanks very much, Jim. I’ll have to look into 2. and 4., but that’s a good start!

Haha, no, all answers welcome! To be honest, I prefer to look through finished code and try to figure out how it works than figure it out myself. But it felt rude to just ask for a solution, and it’s worth trying to figure it out as well. Thanks for your solution, though. I appreciate it. :slight_smile:

Ah, very clever. Nice work. I even almost can understand it. :slight_smile:

1 Like

The CSS order property allows you to change the display order of elements within a CSS Flexbox container or a CSS Grid container. That’s why I changed the CSS of <article> to display: flex.

Fortunately the integer values of the order properties do not need to be sequential 1, 2, 3, 4, 5 and 6.

parseInt(Math.random()*1e9) generates random integers between 0 and 1,000,000,000. I chose a fairly large exponent 9 to tend to minimise the chance of generating two or more identical integers. Actually it does not matter if identical integers are generated: the corresponding paragraphs would be shown in original order. Strictly therefore the paragraphs will not be absolutely perfectly in random order.

The forEach loops through the <p> elements and uses an arrow function to set the CSS order value of each element to a random integer (with implicit type conversion to string). I chose the variable e to represent each element. A traditional for loop could have been used.

Screenreaders will probably read out the paragraphs in original order.

3 Likes

Appending an existing node should move it without needing to clear the content…

2 Likes

Similar to Archibald’s, but using the Javascript logic from James, instead of CSS:

let parent = document.getElementsByTagName("article")[0];
Array.from(document.getElementsByTagName("p")).sort((a,b) => Math.random()-0.5).forEach((x) => parent.appendChild(x));

#AlmostAOneLiner.

4 Likes

Yes, and in Grid too. As it turns out, I am using Grid for the layout of these pages, but that’s an aside.

thanks for the explanation. It was more the JS bit I was getting my head around. I vaguely remember that exponent syntax, but wouldn’t have come up with it myself. :stuck_out_tongue:

I did wonder about that. @james_hibbard — Is there a particular reason you suggested clearing the content explicitly, or a best way to do it explicitly?

Nice one. Thanks!

Not really. It seemed logical to me, but as Marc says, not absolutely necessary.

1 Like

Forgot about that one :slight_smile:

Is there a particular reason you suggested clearing the content explicitly

In that vein I wonder if there are any benefits to using a documentFragment for this type of operation, or whether it’s overkill in this instance.

const article = document.querySelector('article')
const fragment = document.createDocumentFragment()

Array
  .from(
    article.querySelectorAll('p'), 
    (elem) => elem.cloneNode(true) // map cloned nodes
  )
  .sort((a, b) => Math.random() - 0.5)
  .forEach((elem) => fragment.appendChild(elem))

article.replaceChildren(fragment) // less reflows?
1 Like

I mean, is it a cryptographically secure true randomness? No. Is it short and will it do for scrambling a basic array? Sure. :slight_smile:

2 Likes