Read random chapters of the Daodejing

Hello friends! Check out my Mixed Messages project which I’ve published under the name Dao Drip.

Link: https://daod.rip/
Repo: https://github.com/ptrrdrck/daod.rip

This project began under the guidelines of the Codecademy mixed messages challenge and has since taken on a life of its own. The original result displayed a random chapter of the Daodejing (an ancient philosophical text) from three different translations. People enjoy reading various translations of the text because, in English, they can differ quite a bit from each other and together they can give us more insight into the Chinese scrolls that were discovered. I enjoyed the project so much that I slowly added additional features and more translations to create a resource that a curious mind might find more useful. The work of achieving the various features was very rewarding in terms of new skills and understanding of this level of web development. I invite you to “drip” a few chapters into your life and explore my code! Feedback and any collaborative ideas are most welcome! I’m especially interested in any thoughts on how to shorten or combine repeated code segments in script.js. Thanks for reading :slight_smile:

1 Like

I like it! However, there’s a lot of magic here. Meaning you have numbers in your script that make assumptions about your data that should be derived directly from your data.

So, rather than:

const allTranslations = [ value, value, value...];

Instead, pull that from the data:

const allTranslations = Object.keys(dao);
const translationLines = dao[allTranslations[0]].length; // we'll come back to this

Rather than:

let rand = randNumb(dao["Stephen Mitchell"].length);

Instead:

const rand = randNumb(translationLines);

Instead of readChapters and unreadChapters, pick one. They should be mutually exclusive. I’d go with readChapters.

Then unreadChapters becomes a function, if you need it. You’d never update it, of course: only readChapters.

e.g.

const unreadChapters = () => 
    new Array(translationLines)
        .fill(0)
        .map((_,i) => i + 1)
        .filter(x => !readChapters.includes(x));

There’s a lot of stuff floating about in the global space. Try to wrangle all your stuff into a single function, or at least a single spot.

Keep at it. Have fun.

@baavgai Thanks for the suggestions mate! I implemented the bit about the translation variables.

I understand setting up unreadChapters as a function, but I need to use the variable version to display the items in that array and also update local storage. Do you see a way to do that with unreadChapters as a function?

but I need to use the variable version to display the items

I honestly don’t understand this objection.

Looking over the code again, you only ever reference unreadChapters in displayUnreadChapters. Knowing this, you don’t even need an extra function, you can apply the logic in that one function:

function displayUnreadChapters() {
  document.getElementById("unread-chapters").remove();

  // give this a reasonable name so you can use it on the last append
  const root = document.createElement("TABLE");
  root.setAttribute("id", "unread-chapters");
  tablePlaceholder.appendChild(root);

  // loop over total chapters
  for (let unreadChapter = 1; unreadChapter <= totalChapters; unreadChapter++) {
    // check to see if it's read
    if (!readChapters.includes(unreadChapter)) {
      // if not read, do some processing
      const w = document.createElement("A");
      w.setAttribute("href", `javascript:selectedChapter = ${unreadChapter}; viewChapter(${unreadChapter} - 1);`);
      w.classList.add("chapter-link");
      w.innerText = `${unreadChapter}`;
      const y = document.createElement("TD");
      y.appendChild(w);
      root.appendChild(y);
    }
  }
}

Got it. Really helpful lesson to eliminate a variable when it can be part of the for loop’s logic! Thank you @baavgai :metal:

If you have another moment, do you have any thoughts on a more efficient way to combine the translation checkbox if statements (lines 408-448)? And/or the translation checkbox event listeners (lines 495-556)? I feel like there may be a more elegant way to do all that without the repitition.

You have a mix of programmatic DOM generation and static HTML elements that you have to read in. It would be easier if you simply committed to the DOM generation overall. As it stands, the static HTML means any change to the data means you need change that as well. In many places. Which is not good.

If you generated all those check boxes, then you could assign ids and be consistent in your code without worry. Indeed, they could be standardized with just the index, e.g. const checkBoxId = "trans-check-" + index;.

For the bottom bit, if you have a list of them, you can iterate over it. e.g.

[
    { checkBoxId: "mitchell-checkbox", name: "Stephen Mitchell" },
    { checkBoxId: "fengEnglish-checkbox", name: "Gia-Fu Feng & Jane English" },
    { checkBoxId: "addissLombardo-checkbox", name: "Stephen Addiss & Stanley Lombardo" },
    { checkBoxId: "lin-checkbox", name: "Derek Lin" },
    { checkBoxId: "legge-checkbox", name: "James Legge" },
    { checkBoxId: "leguin-checkbox", name: "Ursula K. Le Guin" },
    { checkBoxId: "lau-checkbox", name: "D. C. Lau" },
].forEach(({ checkBoxId, name }) => {
    document.getElementById(checkBoxId).addEventListener("change", () => {
        toggleArrayItem(selectedTranslations, name);
        refreshCurrentChapter();
        localStorage.setItem("selectedTranslations", JSON.stringify(selectedTranslations));
    });
});

For the first half, stuff like localStorage.setItem("addissLombardo-checkbox", "true");, is exceptionably cumbersome and doesn’t lend itself to the data driven thing. You should have an array like you do with readChapters. If it’s in, it’s true. Or even a boolean array you can toggle.

There is a concept of state. Essentially all the moving parts in you app. All the things that can change. The more things that can change, the more complexity you have to deal with.

In pure JS, like this, you can pretty much do whatever makes sense to you, for good or ill. If you’re doing something with React, or Vue, or Angular, or pretty much any JS framework, the idea of state will be hammered home strongly.

Gather all the stuff that you think should be in localStorage into an object. That is your basic state. Every time you change that, call a function that renders your view based on current state. Thinking about things this way may help with controlling that repetition.

Thank you so much for this feedback. I understand everything you’ve said and am going to move in this direction. Cheers.