Last active
March 21, 2018 00:27
-
-
Save rpgraham84/89085a0496c77178ad5584a31913f8b3 to your computer and use it in GitHub Desktop.
Hacker News sort by best
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
// This let statement selects elements from the HTML document using a CSS selector. | |
// Specifically, we're making an Array of all table rows (each news listing is | |
// a table row in an html table). The css selector here says, find the 'tr' elements | |
// that are nested at any depth below a tbody which is nested at any depth below any | |
// element bearing the class 'itemlist' -- the dot in front of itemlist indicates | |
// we're talking about a CSS class, not an element. | |
// Look up css selectors and the document.querySelectorAll | |
// function for more about that. | |
// When you do a querySelectorAll, you get a NodeList in return, not quite the same | |
// thing as an array, because you can't slice it. Well, maybe you could using css selectors | |
// but lets not get into that. The 'Array.from' here converts our nodelist into and array | |
// we can slice on the very next line. | |
let elements = Array.from(document.querySelectorAll(".itemlist tbody tr")); | |
// Since we'll be taking each news entry as a group of 3 tr's, we want to take the slice of | |
// the tr's that is divisible by 3, the remaining 0-2 elements we'll call 'leftovers' | |
// and append them back to the table after after everything else is done. | |
let leftoverCount = elements.length - elements.length % 3; | |
// Here, we're making a new iterator of the 'elements' array from the line above. | |
// entries() is a built-in function that iterates over each element in the array and | |
// pairs it with it's numeric index. So ['a', 'b'] becomes [[0, 'a'], [1, 'b']]. | |
let ielements = elements.slice(0, leftoverCount).entries(); | |
// Here we save the leftovers to be appended at the end. | |
let leftovers = elements.slice(leftoverCount); | |
// Just initializing some variables that will be used in the upcoming lines. | |
// 'i' will be the index variable and 'e' will be the HTMLElement as we iterate through. | |
// ielements from the line above. 'score' is the score of the line | |
let i, e, score, rows = [], tbody = document.createElement("tbody"); | |
// This is some hot new es6 magic right here. Remember how ielements turned our elements | |
// array into an array of 2-element arrays, each containing the index and element | |
// from the original array? Well, now we're unpacking that shit. You could implement | |
// the same behavior using a simple counter variable starting at 0 and ticking up by | |
// one with each iteration. I just find this approach more elegant and closer to the | |
// way I would do it in Python. At the start of each iteration in this for loop, 'i' will | |
// be the index and 'e' will be the table row element we want to examine. See | |
// Destructuring assignment https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment | |
for ([i, e] of ielements) { | |
// Each news entry is represented as 3 consecutive 'tr' elements and the middle tr contains the sweet, juicy | |
// score value we want to sort by. So, as we iterate over all the elements, we'll skip the 0th, take | |
// the 1st, skip the 2nd, skip the 3rd, take the 4th. But what function represents the selection we | |
// want to make? Mod division n % d gives you the remainder of n / d. Watch what happens: | |
// 0 % 3 = 0 | |
// 1 % 3 = 1 | |
// 2 % 3 = 2 | |
// 3 % 3 = 0 | |
// 4 % 3 = 1 | |
// 5 % 3 = 2 | |
// 6 % 3 = 0 | |
// Doing a mod division by 3 for all the natural numbers creates a repeating cycle of 0, 1, 2 for all the | |
// numbers going on forever. In fact, replace 3 for n and the cycle will always be 0, 1, 2... up to n - 1. | |
// Since we always want the middle element out of a set of 3, we want to take the element each time | |
// the counter mod 3 equals 1. | |
if (i % 3 === 1) { | |
// So we're inside this if statement, so 'e' here must be one of those middle elements we were | |
// looking for. We find the element with the css class of 'score' within our element, 'e' and | |
// assign it to the variable scoreTag. Remember that querySelectorAll returns an Array, so | |
// we index into it at its zeroth position to take what we assume is to be the only element in | |
// the array. Which we can safely assume it will be, unless HN changes their layout. | |
let scoreTag = e.querySelectorAll(".score")[0]; | |
// Now that we have the score element, we need to extract the raw integer value of the score so that | |
// we can sort the rows based on it. There are 2 big concepts introduced on this line. Ternary | |
// Expressions and Regular Expressions (regexes). This line is dense. | |
// A ternary expression is shorthand for doing a simple variable assignment based on an | |
// if statement. It follows the format: | |
// result = (condition)?expr1:expr2 | |
// where condition is evaluated as a boolean true/false value and if it is true, then result will get | |
// the value of expr1, otherwise result will be expr2. | |
// We know that scoreTag will either be an HTMLElement or undefined. HTMLElement will evaluate to true, | |
// undefined to false. If true, we're saying here we want to parse an integer out of whatever our regex | |
// matched when executed on our scoreTag's innerText (the visible score text, ie. "27 points"). | |
// Otherwise, we assume a score of 0. This places any abnormal content we may encounter at the bottom | |
// of the list. But the 'abnormal content' is hypothetical and should never actully exist. | |
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator | |
// Okay now regexes are cool as shit. It's a whole language (or more broadly outside of js, a collection | |
// of languages) that describe how to parse text and extract information. Like, grab all the digits | |
// or find all words that start with a certain prefix within some text. Can't type it all here, | |
// if you read any of these links, read the shit out of this one. It's insanely powerful. You will | |
// be capable of things that mere mortals will worship you for. | |
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions | |
// Putting it together now, the parseInt function converts a string to an integer given some base. | |
// In this case, being that we're normal humans, we want to use base 10. Yes it is mandatory to specify that. | |
// So, we're defining the regex, /\d+/ which says grab the first sequential set of digits. If we executed that | |
// on scoreTag.innerText, and the text was "27 points", we'd have the string "27". So, parseInt would | |
// process that string and return the number 27 and that's what would be our score. Phew. | |
let score = scoreTag ? parseInt(/\d+/.exec(scoreTag.innerText)[0], 10) : 0; | |
// Now that we have a score with a numeric value, we could sort a list of rows on that value. | |
// Lets begin by building that collection of rows with their score. | |
// We defined a 'rows' variable above as an empty array []. Now, we'll push arrays to it that contain | |
// the score and an array of the 3 table rows that make up this news headline. | |
// Note, we're indexing into the original elements (not ielements). | |
rows.push([score, [elements[i - 1], elements[i], elements[i + 1]]]); | |
} | |
} | |
// Now we delete the table body with all the table rows in it. | |
document.querySelectorAll(".itemlist tbody")[0].remove(); | |
// The variable we created, tbody, is an HTMLElement living in memory in JS, | |
// it needs to be actually appended to the HTML document so we can see it. | |
// That whole living in memory thing really means its not attached to the | |
// Document Object Model, or DOM. This line brings it into the DOM | |
// by appending it as a child to the element with the class itemlist. | |
// (The same place the last tbody lived before we removed it) | |
document.querySelectorAll(".itemlist")[0].appendChild(tbody); | |
// In some languages, sorting is not a huge pain in the ass. In JS, it just is... | |
// rows.sort((a, b) => {b[0] - a[0]}) | |
// This line is sorting the rows we built on line 92 based on their score values. | |
// Sorting in JS is lexicographic by default and isn't smart enough to figure out | |
// you're giving it numbers, so it will interpret your numbers as strings and | |
// sort them as such by default. So instead we pass a custom compare function in as | |
// an argument to rows.sort. This function is written out here as an es6 arrow function. | |
// These compare functions are supposed to take to elements from the array being | |
// sorted and return a boolean that determines sort order for the elements, or something. | |
// Good one to read up on. | |
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort | |
for ([score, elements] of rows.sort((a, b) => { return b[0] - a[0]; })) { | |
// Okay so we're walking through the sorted news headlines now, each one is a collection of 3 | |
// HTMLElements, so we iterate each of those and append them to our table body. | |
for (let ele of elements) { | |
if (ele) tbody.appendChild(ele); | |
} | |
} | |
// Finally, we iterate over the leftover elements and append them to the tbody. | |
for (let ele of leftovers) { | |
if (ele) tbody.appendChild(ele); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment