Reputation:
I have an unordered list of articles, each with a heading and a paragraph. I would like to sort these articles alphabetically using JavaScript, but I get an unexpected result.
The JavaScript sorting function that I am using (originally taken from: https://www.w3schools.com/howto/tryit.asp?filename=tryhow_js_sort_list) can be seen below:
function sort() {
var list, i, switching, b, shouldSwitch;
list = document.getElementById("courses");
switching = true;
/*Make a loop that will continue until
no switching has been done:*/
while (switching) {
//start by saying: no switching is done:
switching = false;
b = list.getElementsByTagName("LI");
//Loop through all list-items:
for (i = 0; i < (b.length - 1); i++) {
//start by saying there should be no switching:
shouldSwitch = false;
/*check if the next item should
switch place with the current item:*/
if (b[i].innerHTML.toLowerCase() > b[i + 1].innerHTML.toLowerCase()) {
/*if next item is alphabetically
lower than current item, mark as a switch
and break the loop:*/
shouldSwitch= true;
break;
}
}
if (shouldSwitch) {
/*If a switch has been marked, make the switch
and mark the switch as done:*/
b[i].parentNode.insertBefore(b[i + 1], b[i]);
switching = true;
}
}
}
The full code can be found at: https://jsfiddle.net/MihkelPajunen/c37m0w1s/1/
The sorted order should be:
My question is twofold: how come my supposedly sorted articles look the way they do, and what should I change to produce the alphabetical order that I am after?
Upvotes: 1
Views: 570
Reputation: 166
In addition to the other answers, here's a greatly simplified version of your code. Readability can still be improved. I've added quite a bit of extra information with links to the Mozilla Developer Network (MDN) documentation to show you how much information is missing on the W3Schools site.
function sort () {
let container = document.getElementById('courses')
Array.from(container.querySelectorAll('li > article > h2:first-child'))
.sort((h2_a, h2_b) => h2_a.innerText.localeCompare(h2_b.innerText))
.forEach(h2 => container.appendChild(h2.parentElement.parentElement))
}
article {
border: 1px solid black;
margin: 10px;
padding: 10px;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Sorting Articles</title>
</head>
<body>
<!-- Hit the button to sort items (at least something happens) -->
<button onclick="sort()">Sort list items</button><br>
<!-- This section contains six articles-->
<section>
<h1>All courses:</h1>
<!-- The articles are structured in an unordered list-->
<ul id="courses">
<li>
<!-- Each list item is a complete article -->
<article>
<h2>HTML (1)</h2>
<p>Some text about the HTML course.</p>
</article>
</li>
<li>
<article>
<h2>CSS (2)</h2>
<p>Some text about the css course.</p>
</article>
</li>
<li>
<article>
<h2>JavaScript (3)</h2>
<p>Some text about the JavaScript course.</p>
</article>
</li>
<li>
<article>
<h2>Ruby (4)</h2>
<p>Some text about the Ruby course.</p>
</article>
</li>
<li>
<article>
<h2>Python (5)</h2>
<p>Some text about the Python course.</p>
</article>
</li>
<li>
<article>
<h2>Java (6)</h2>
<p>Some text about the Java course.</p>
</article>
</li>
</ul>
</section>
</body>
</html>
Retrieve a reference to the parent container element
let container = document.getElementById('courses')
Generate a list of references to all title elements.
container.querySelectorAll('li > article > h2:first-child')
The .querySelectorAll() function makes use of CSS selector syntax.
This returns a NodeList instance, which is limited in functionality compared to an Array instance. To add the functionality described in the next two steps, you simply apply the transformation.
Array.from(......)
Sort the list of elements.
.sort((h2_a, h2_b) => h2_a.innerText.localeCompare(h2_b.innerText))
The .sort() function gets a custom comparison function (here shortened into an arrow function) as argument, which tells the sort function to compare the .innerText
of each <h2>
pair instead of the HTMLElement objects themself. See the answer of Travis J for more info as to why you shouldn't use .innerHTML
.
The list only contains references to these <h2>
elements, which means their content doesn't get copied around while sorting. This wasn't the case either in your original post, it's just a heads up.
Reinsert the <li>
elements using .appendChild().
.forEach(h2 => container.appendChild(h2.parentElement.parentElement))
This first removes the <li>
(parentElement) item from the document and reinserts it at the end of its parent, <ul>
, meaning there will never be two of the same items in this list.
The .forEach() function simply applies this action to every item of the array.
Array.from() // #courses li > article > h2
.sort() // h2.innerText -- error was here
.forEach() // appendChild() reinserts it in the end
Upvotes: 1
Reputation: 82297
When comparing text, you should not use innerHTML
.
Use innerText
MDN instead. Here is an updated fiddle with this change made:
if (b[i].innerText.toLowerCase() > b[i + 1].innerText.toLowerCase()) {
I can understand how you came to use it though, since w3schools does. This is one of many reasons why you should completely avoid that website. Another glaring problem they have is the use of document.write. That aside, try to use higher quality examples in the future, and avoid w3schools if you would like to avoid bad practice.
Here are some higher quality content providers:
Upvotes: 1
Reputation: 42354
The problem is ironically with you HTML comment, which gets treated as part of the .innerHTML
. Because the first letter of the comment (<
) is considered to be a 'lesser' ASCII character than C
(<
versus C
), the HTML
article containing the comment gets sorted to the top of the list alphabetically.
Simply removing this comment from your HTML
article causes the elements to sort as expected:
//Script borrowed from: https://www.w3schools.com/howto/tryit.asp?filename=tryhow_js_sort_list
function sort() {
var list, i, switching, b, shouldSwitch;
list = document.getElementById("courses");
switching = true;
/*Make a loop that will continue until
no switching has been done:*/
while (switching) {
//start by saying: no switching is done:
switching = false;
b = list.getElementsByTagName("LI");
//Loop through all list-items:
for (i = 0; i < (b.length - 1); i++) {
//start by saying there should be no switching:
shouldSwitch = false;
/*check if the next item should
switch place with the current item:*/
if (b[i].innerHTML.toLowerCase() > b[i + 1].innerHTML.toLowerCase()) {
/*if next item is alphabetically
lower than current item, mark as a switch
and break the loop:*/
shouldSwitch = true;
break;
}
}
if (shouldSwitch) {
/*If a switch has been marked, make the switch
and mark the switch as done:*/
b[i].parentNode.insertBefore(b[i + 1], b[i]);
switching = true;
}
}
}
article {
border: 1px solid black;
margin: 10px;
padding: 10px;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Sorting Articles</title>
</head>
<body>
<button onclick="sort()">Sort list items</button>
<section>
<h1>All courses:</h1>
<ul id="courses">
<li>
<article>
<h2>HTML (1)</h2>
<p>Some text about the HTML course.</p>
</article>
</li>
<li>
<article>
<h2>CSS (2)</h2>
<p>Some text about the css course.</p>
</article>
</li>
<li>
<article>
<h2>JavaScript (3)</h2>
<p>Some text about the JavaScript course.</p>
</article>
</li>
<li>
<article>
<h2>Ruby (4)</h2>
<p>Some text about the Ruby course.</p>
</article>
</li>
<li>
<article>
<h2>Python (5)</h2>
<p>Some text about the Python course.</p>
</article>
</li>
<li>
<article>
<h2>Java (6)</h2>
<p>Some text about the Java course.</p>
</article>
</li>
</ul>
</section>
</body>
</html>
Upvotes: 1