carene_waterman: An image of the Carina Nebula (Default)
carene_waterman ([personal profile] carene_waterman) wrote2013-09-19 02:59 pm
Entry tags:

Bookmarklet - AO3 Reorder Fandoms

EDIT as at July 22, 2014: I did a quick and dirty update to get this working, but it still needs to be cleaned up. The issue was the AO3 (finally) got rid of presentational HTML classes and started using proper CSS techniques to style the fandoms pages. I was using the .odd and .even classes in my script because they were just so handy. That'll teach me.

So, I wanted to get back into coding after a summer of mostly gardening, and I was not really feeling the Ruby love. I'm a bit stalled mentally on the project in the course I'm doing, so I'll work on it some other time.

I wanted some more practical JavaScript work. I started reading at the AO3 again too, and the very first story I read had that annoying thing where every other paragraph was empty so the effective paragraph margin was doubled.

I knocked off a quick and dirty way to fix that, and I'm now working on making it a little less dirty.

Next, I took on the fandoms pages. Their long lists of fandoms in alphabetical order and when browsing, they are dominated by scores of fandoms with only 1 or 2 works. Yuletide legacy, I would guess. So, I could see the attraction of changing the sort order to numerical to find possible things to read with lots of stories.

And you don't have to read the code or my notes on it at all. To try out the bookmarklet go to The AO3 section on my GitHub page. If you want to read the code etc., keep on reading right here.

Reorder Fandoms


For this project, I actually wrote out the program in plain English first. I copied a section of the HTML from a fandom page into a text editor and then wrote up what I wanted to do to it.

Count the number of .listbox .group sections inside the .index .group ol.

For each of Count => add to an object with the property/value pair equal to the anchor and the number within each li of the .listbox .group. (One object for the whole page of fandoms/counts.)

Sort the object numerically by the value values https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort

Blank the navigation menu and the .index .group ol.

Make a single .listbox .group ul and print each item of the object within it as an li (odd and even?)


I mostly stuck to this. I'd made the first step more complex than it needed to be, and I did decide to keep the odd and even style that the original page uses to make it 2 columns (how many years before people just use the CSS property to do that?). I had also left out a layer of the list box hierarchy so the remade page was another step than I listed here.

The Code


/*Reorder AO3 Fandoms pages*/


/*Make sure we're on the right kind of page and we haven't already run the script*/
var fandomGroup = document.querySelectorAll('ol.fandom.index.group');
if (fandomGroup[0] != undefined && fandomGroup[0].classList.contains('zzz-numerical') == false){
  /*add a marker class to the ol and get all the entry elements*/
  fandomGroup[0].classList.add('zzz-numerical');
  var fandomList = document.querySelectorAll('.odd, .even');
  var numericalList = [];

  /*pull relevent portions from fandomList and fill numericalList setting the count as an integer*/
  function extractLinkcounts (inList) {
    var linkCount = fandomList[inList].innerHTML;
    var endOfLink = linkCount.indexOf('') +4;
    var linkOnly = linkCount.slice(linkCount.indexOf('http'), linkCount.indexOf('"', linkCount.indexOf('http')));
    var linkText = linkCount.slice(linkCount.indexOf('>') + 1, linkCount.lastIndexOf('<'));
    var countPortion = linkCount.substr(endOfLink);
    var countOnly = countPortion.slice(countPortion.indexOf('(') + 1, countPortion.indexOf(')'));
    numericalList.push ({href: linkOnly, text: linkText, count: +countOnly});
  }

  for (var i = 0 ; i < fandomList.length; i ++) {
     extractLinkcounts (i);
  }

  /*Sort the list in descending order. To change to ascending make it: return a.count - b.count.*/
  numericalList.sort(function(a, b) {
      return b.count - a.count;
  });

  /*blank the navigation and listbox group on the page*/
  var alphabetElems = document.querySelectorAll('.letter.listbox.group, .alphabet.navigation')
  for (var i = 0; i < alphabetElems.length; i ++) {
    alphabetElems[i].style.display = 'none';
  }

  /*Make a new listbox li, put in a heading and nest in an index group ul.*/
  var fandomNumerical = document.createElement ('li');
  fandomNumerical.setAttribute('class', 'letter listbox group');
  var numericalHeading = document.createElement('h3');
  numericalHeading.setAttribute('class', 'heading');
  numericalHeading.innerHTML = "Descending Order by Number of Works";
  fandomNumerical.appendChild(numericalHeading);
  var fandomIndex = document.createElement('ul');
  fandomIndex.setAttribute('class', 'tags index group');
  fandomGroup[0].appendChild(fandomNumerical);
  fandomNumerical.appendChild(fandomIndex);
  
  /*Make an li for each sorted entry and give them the odd or even class.*/
  for (var i = 0; i < numericalList.length; i ++) {
    var fandomItem = document.createElement('li');
    if (i % 2 == 0) {
      fandomItem.setAttribute('class', 'odd');
      }
      else {
        fandomItem.setAttribute('class', 'even');
        }
    /*recreate the anchor and count text and put it inside the li (some pages have nothing for number of works, ensure it isn't created with a 0 count*/
    var fandomAnchor = document.createElement('a');
    fandomAnchor.setAttribute('href', numericalList[i].href);
    fandomAnchor.setAttribute('class', 'tag');
    fandomAnchor.innerHTML = numericalList[i].text;
    fandomItem.appendChild(fandomAnchor);
    if (numericalList[i].count > 0) {
      var fandomCount = document.createTextNode(' (' + numericalList[i].count + ')');
      fandomItem.appendChild(fandomCount);
      }
    fandomIndex.appendChild(fandomItem);
  }
}


How it Works



The code starts with the last thing I added, a test to ensure we're on the right sort of page and that the script hasn't been run. It just fails silently otherwise, which on balance, I think is the right choice for a bookmarklet.

I used the zzz prefix to my made up class names to avoid any conflicts with site CSS.

Next, I have a function that pulls apart the node list of li elements and makes an array of objects with the href, the link text and the amount of works as an integer as values for properties.

I did this in a really hacky and clunky way. I should have used some kind of regexp to pull out just the number of works and saved the whole li HTML alongside it, but I was basing my not recommended method of slicing up the string on the fact that the format of the code never changes.

I might change this and see if the performance increases.

I also set this up as a function I then call just for readability and testing.

Next the array is sorted, which the Mozilla page explains really well and has good examples.

Next I get the alphabet pagination element and the ul elements containing the original links and set them all to display none.

Then I create a new set of nested elements including a new heading that will look just like the original listboxes by virtue of using the same CSS classes.

Then I go through the sorted array and remake each
  • for each one with the odd or even class exactly backwards. (Since an array starts with 0, the first one has an even index and class odd and so on.) Then I fill them with the reconstituted HTML for the links. This is the point at which I wish I'd done the extraction differently.

    But, this was good practice in making DOM elements. And why did I do it that way? Good old laziness. I knew how to do it the way I did without looking it up. The regexp method would have involved more research and testing.

    It works, there's certainly better ways to do it, and it could use some performance boosting since these pages are sometimes very long, but for now it is what it is.

    And then there's the issue of creating a compressed version to make a bookmarklet with that has enough escaped characters in it to be postable on a web page. I really need a script to do that.