2
\$\begingroup\$

I've created a numbering system that compliments another javascript library for numbering documentation.

I know this can be done in CSS but because this gets rendered and outputted as static HTML afterwards this is the least client side rendering opposed to some CSS bugs I was fighting.

I'm particularly interested in the javascript section for review, as the HTML and CSS are here for demo.

I don't like this section:

const sidebarListItemsH1 = document.querySelectorAll('div.sidebar-nav > ul > li');
const sidebarListItemsH2 = document.querySelectorAll('div.sidebar-nav > ul > li > ul > li');
const sidebarListItemsH3 = document.querySelectorAll('div.sidebar-nav > ul > li > ul > li > ul > li');
const sidebarListItemsH4 = document.querySelectorAll('div.sidebar-nav > ul > li > ul > li > ul > li > ul > li');
const sidebarListItemsH5 = document.querySelectorAll('div.sidebar-nav > ul > li > ul > li > ul > li > ul > li > ul > li');
const sidebarListItemsH6 = document.querySelectorAll('div.sidebar-nav > ul > li > ul > li > ul > li > ul > li > ul > li > ul > li');

But I don't know of a better way of doing it.

I tried to loop over the li element, and then if it was an ul add a different number. But everything I had tried wasn't working or returning empty values.

// dummy settings
const startLevel = 1; // top most heading level
const finishLevel = 6; // bottom most heading level
const separator = "."; // what separates the numbers
let dummyNumbers = "1.0.0.0.0.0"; // starting numbers

// manipulation
dummyNumbers = dummyNumbers.split(separator);
dummyNumbers = dummyNumbers
  .concat(new Array(6).fill(0))
  .slice(0, 6)
  .map((x) => +x);

// -- get the navigation sidebar items
const sidebarList = document.querySelectorAll("div.sidebar-nav ul");

// -- for all the <ul> items
sidebarList.forEach((listItem) => {
  // set the previous item
  let previousElement = listItem.previousElementSibling;

  // exit if null
  if (!previousElement) return;

  // if <ul> and previous was <li>
  if (
    listItem.parentNode.nodeName.toLocaleUpperCase() == "UL" &&
    previousElement.nodeName.toLocaleUpperCase() == "LI"
  ) {
    // -- move it into the previous
    previousElement.appendChild(listItem);
  }
});


// -- get the elements
const sidebarListItemsH1 = document.querySelectorAll('div.sidebar-nav > ul > li');
const sidebarListItemsH2 = document.querySelectorAll('div.sidebar-nav > ul > li > ul > li');
const sidebarListItemsH3 = document.querySelectorAll('div.sidebar-nav > ul > li > ul > li > ul > li');
const sidebarListItemsH4 = document.querySelectorAll('div.sidebar-nav > ul > li > ul > li > ul > li > ul > li');
const sidebarListItemsH5 = document.querySelectorAll('div.sidebar-nav > ul > li > ul > li > ul > li > ul > li > ul > li');
const sidebarListItemsH6 = document.querySelectorAll('div.sidebar-nav > ul > li > ul > li > ul > li > ul > li > ul > li > ul > li');

// -- add the attributes
sidebarListItemsH1.forEach(item => {
  item.setAttribute("heading", "1");
});
sidebarListItemsH2.forEach(item => {
  item.setAttribute("heading", "2");
});
sidebarListItemsH3.forEach(item => {
  item.setAttribute("heading", "3");
});
sidebarListItemsH4.forEach(item => {
  item.setAttribute("heading", "4");
});
sidebarListItemsH5.forEach(item => {
  item.setAttribute("heading", "5");
});
sidebarListItemsH6.forEach(item => {
  item.setAttribute("heading", "6");
});

const sidebarListItems = document.querySelectorAll('div.sidebar-nav li');

const headingStartingNumbers = [
  0, // null
  dummyNumbers[0] - 1, // h1
  dummyNumbers[1] - 1, // h2
  dummyNumbers[2] - 1, // h3
  dummyNumbers[3] - 1, // h4
  dummyNumbers[4] - 1, // h5
  dummyNumbers[5] - 1, // h6
];

// sidebarListItems.forEach( (item) => {
for (var item in sidebarListItems) {

  let numberText = '';
  let heading = sidebarListItems[item];

  // -- regex to match on
  const headingRegex = new RegExp(
    `^([${startLevel}-${finishLevel}])$`
  );

  // element does not match a heading regex
  if (!heading ||
    !heading.tagName ||
    !heading.getAttribute('heading').match(headingRegex)
  ) continue;

  // -- set the number value from the added attribute
  const headingNumber = (
    parseInt(heading.getAttribute('heading'), 10) || 0
  );

  // return the heading level number
  headingStartingNumbers[headingNumber]++;

  // loop from 1-6 (H1 to H6)
  for (var levelNumber = 1; levelNumber <= 6; levelNumber++) {

    // -- if loop index less than attribute heading number
    if (levelNumber <= headingNumber) {

      // -- add the number to the variable
      numberText += headingStartingNumbers[levelNumber] + separator

      // -- exit out if out of range
    } else {
      continue;
    }
  }

  // add to the text
  heading.querySelector('a').innerHTML = numberText + ' ' + heading.querySelector('a').innerHTML
}
body {
  padding: 3em;
}

pre {
  background: #eee;
  padding: 1em;
}

aside {
  left: 0 !important;
  margin: 0 !important;
  transform: none !important;
}
<aside class="sidebar">
  <div class="sidebar-nav">
    <ul>
      <li><a>Title 1</a></li>
      <ul>
        <li><a>Title 2</a></li>
        <ul>
          <li><a>Title 3</a></li>
          <ul>
            <li><a>Title 4</a></li>
            <ul>
              <li><a>Title 5</a></li>
              <ul>
                <li><a>Title 6</a></li>
              </ul>
            </ul>
          </ul>
        </ul>
      </ul>
      <li><a>Title 1</a></li>
      <ul>
        <li><a>Title 2-1</a></li>
        <li><a>Title 2-2</a></li>
        <ul>
          <li><a>Title 3-1</a></li>
          <li><a>Title 3-2</a></li>
          <ul>
            <li><a>Title 4-1</a></li>
            <li><a>Title 4-2</a></li>
            <ul>
              <li><a>Title 5-1</a></li>
              <li><a>Title 5-2</a></li>
              <ul>
                <li><a>Title 6-1</a></li>
                <li><a>Title 6-2</a></li>
              </ul>
            </ul>
          </ul>
        </ul>
      </ul>
    </ul>
  </div>
</aside>

Expected outputs:

Expected output:
// seqential numbers
1. Title 1
1.0. Title 2
1.0.0. Title 3
1.0.0.0. Title 4
1.0.0.0.0. Title 5
1.0.0.0.0.0. Title 6

// nested numbering
2. Title 1
2.0. Title 2-1
2.1. Title 2-2
2.1.0. Title 3-1
2.1.1. Title 3-2
2.1.1.0. Title 4-1
2.1.1.1. Title 4-2
2.1.1.1.0. Title 5-1
2.1.1.1.1. Title 5-2
2.1.1.1.1.0. Title 6-1
2.1.1.1.1.1. Title 6-2

// altering the dummyNumbers and separator
// dummyNumbers = 30.1.5.8.9.3
// separator = '-'
30- Title 1
30-1- Title 2
30-1-5- Title 3
30-1-5-8- Title 4-1
30-1-5-9- Title 4-2
30-1-5-9-0- Title 5
30-1-5-9-0-0- Title 6

// altering start and finish numbers
// startLevel = 2
// finishLevel = 4
Title 1
1.0. Title 2
1.0.0. Title 3-2
1.0.1. Title 3-2
1.0.1.0. Title 4
Title 5
Title 6-1
Title 6-2
\$\endgroup\$
2
  • 1
    \$\begingroup\$ The expected output is a bit strange. Should the count of the nested elements not reset when moving on to a next <ul>? At the moment you have 1.0 and 2.1. Should 2.1 not start at 2.0? Or should 1.0 not start at 1.1? What if there is both an 1.0 and 1.1 present? Should the fist element under 2 then be 2.2? \$\endgroup\$
    – 3limin4t0r
    Commented Dec 30, 2021 at 21:06
  • \$\begingroup\$ @3limin4t0r - error on my part of labelling output. It should all start at .0 and then increment from that. I'll update it now \$\endgroup\$
    – markb
    Commented Jan 1, 2022 at 3:11

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.