Skip to main content

Erratic behavior from an if-else block

I am a hobbyist/novice.

This is very difficult to describe, I can't seem to wrap my head around what is actually happening and that is making the problem difficult to debug.

I have my script divided into three objects and a class, each object holds Elements of an HTML page, Event Listeners, and Functions. I will ask about the merits and flaws of this organization in a separate question.

This project is meant to provide an easy reference for the DM of a tabletop RPG called "Apocalypse World" which is pretty basic. On this webpage, I perform a few different actions but the one in question is the basic moves reference. I have a drop-down list that has the name of the moves, you pick one, and it displays a short summary of the move. This seems to work fine in my first iteration but I wanted to add a feature where selecting the move that is already being displayed will make that move toggle a hidden class that will remove the summary from view. Displaying the first summary seems to work and hiding the element seems to work, but when you try to display a different element both the selected element and the previous element are displayed and have to be removed individually.

Truncated Code

  • The complete code can be found here.
  • The offending code is located in script.js: Functions.basicMoves.displayBasicMove

script.js

// HTML Elements
const Elements = {
  ... ,
  basicMoves: {
    selector: document.querySelector("#basicMovesSelector"),
    button: document.querySelector("#getMove"),
    // 1. The currentElement holds the element that was last revealed.
    currentElement: null
  }
};

// Event Listeners
const Listeners = {
  ... ,
  basicMoves: {
    l1: Elements.basicMoves.button.addEventListener("click", (e) => { Functions.displayBasicMove(e) })
  }
};

// Functions
const Functions = {
  ... ,
  displayBasicMove: (e) => {
    e.preventDefault();

    // 2. The next element to be revealed or the current element is selected
    const newElement = document.querySelector(`#${Elements.basicMoves.selector.value}`);

    // This is the offending if-else block.
    if (Elements.basicMoves.currentElement === null) {
    // 3. If currentElement is null, make the new element the current element, then toggle visibility on that element.
      Elements.basicMoves.currentElement = newElement;
      Elements.basicMoves.currentElement.classList.toggle("hidden");
    } 
    else if (Elements.basicMoves.currentElement ===  newElement) {
      // 4. If the same element is selected, toggle the hidden class on that element.
      Elements.basicMoves.currentElement.classList.toggle("hidden");
    } 
    else {
      // 5. If currentElement is NOT null AND NOT the same element, toggle the hidden class on the current element, set the new element as the current element, then toggle that element.
      Elements.basicMoves.currentElement.classList.toggle("hidden");
      Elements.basicMoves.currentElement = newElement;
      Elements.basicMoves.currentElement.classList.toggle("hidden");
    }

  }
};

index.html

<!DOCTYPE html>
<html>

<head>
  ...
</head>

<body>
  <h1>Apocalypse World MC Utilities</h1>

  ...

  <div id="basicMoves">
    <form>
      <h2>Basic Moves</h2>
      <select id="basicMovesSelector">
        <option value="under-fire">Act Under Fire</option>
        <option value="aggro">Go Aggro</option>
        <option value="sucker">Sucker Somebody</option>
        <option value="seize">Seize by Force</option>
        <option value="seduce-manipulate">Seduce or Manipulate</option>
        <option value="read-sitch">Read a Sitch</option>
        <option value="read-person">Read a Person</option>
        <option value="open-brain">Open Your Brain</option>
        <option value="help-interfere">Help or Interfere</option>
        <option value="lifestyle">Lifestyle Upkeep</option>
        <option value="work-gig">Work a Gig</option>
        <option value="session-end">Session End</option>
      </select>
      <button id="getMove">Show Move</button>
    </form>
    <div id="basicMovesDescriptors">
      <div class="hidden" id="under-fire">
        <p>roll+cool</p>
        <p>On a 10+, you do it.</p>
        <p>On a 7–9, you flinch, hesitate, or stall:
          the MC can offer you a worse outcome, a hard bargain, or an ugly
          choice.</p>
        <p>On a miss, be prepared for the worst.</p>
      </div>

      <div class="hidden" id="aggro">
        <p>roll+hard</p>
        <p>On a 10+, they have to choose 1:</p>
        <p>• Force your hand and suck it up.</p>
        <p>• Cave and do what you want.</p>
        <p>On a 7–9, they can choose 1 of the above, or 1 of the following:</p>
        <p>• Get the hell out of your way.</p>
        <p>• Barricade themselves securely in.</p>
        <p>• Give you something they think you want, or tell you what you want to hear.</p>
        <p>• Back off calmly, hands where you can see.</p>
        <p>On a miss, be prepared for the worst.</p>
      </div>

      <div class="hidden" id="sucker">
        <p>When you attack someone unsuspecting or helpless, ask the
          MC if you could miss. If you could, treat it as going aggro, but your
          victim has no choice to cave and do what you want. If you couldn’t,
          you simply inflict harm as established.</p>
      </div>

      <div class="hidden" id="seize">
        <p>To seize something by force, exchange harm, but first roll+hard.
        <p>On a 10+, choose 3. On a 7–9, choose 2. On a miss, choose 1:</p>
        <p>• You inflict terrible harm (+1harm).</p>
        <p>• You suffer little harm (-1harm).</p>
        <p>• You take definite and undeniable control of it.</p>
        <p>• You impress, dismay, or frighten your enemy.</p>
      </div>

      <div class="hidden" id="seduce-manipulate">
        <p>roll+hot</p>
        <p>For NPCs: on a 10+, they’ll go along with you, unless or
          until some fact or action betrays the reason you gave them.</p>
        <p>On a 7–9,
          they’ll go along with you, but they need some concrete assurance,
          corroboration, or evidence first.</p>
        <p>For PCs: on a 10+, both.</p>
        <p>On a 7–9, choose 1:</p>
        <p>• If they go along with you, they mark experience.</p>
        <p>• If they refuse, erase one of their stat highlights for the remainder of
          the session.</p>
        <p>What they do then is up to them.</p>
        <p>On a miss, for either NPCs or PCs, be prepared for the worst.</p>
      </div>

      <div class="hidden" id="read-sitch">
        <p>roll+sharp</p>
        <p>On a hit, you can ask the MC questions.
          Whenever you act on one of the MC’s answers, take +1.</p>
        <p>On a 10+, ask 3. On a 7–9, ask 1:</p>
        <p>• Where’s my best escape route, way in, or way past?</p>
        <p>• Which enemy is most vulnerable to me?</p>
        <p>• Which enemy is the biggest threat?</p>
        <p>• What should I be on the lookout for?</p>
        <p>• What’s my enemy’s true position?</p>
        <p>• Who’s in control here?</p>
        <p>On a miss, ask 1 anyway, but be prepared for the worst.</p>
      </div>

      <div class="hidden" id="read-person">
        <p>roll+sharp</p>
        <p>On a 10+, hold 3.</p>
        <p>On a 7–9, hold 1.</p>
        <p>While you’re interacting with them,
          spend your hold to ask their player questions, 1 for 1:</p>
        <p>• Is your character telling the truth?</p>
        <p>• What’s your character really feeling?</p>
        <p>• What does your character intend to do?</p>
        <p>• What does your character wish I’d do?</p>
        <p>• How could I get your character to —?</p>
        <p>On a miss, ask 1 anyway, but be prepared for the worst.</p>
      </div>

      <div class="hidden" id="open-brain">
        <p>roll+weird</p>
        <p>On a hit, the MC tells you something new and interesting
          about the current situation, and might ask you a question or two;
          answer them.</p>
        <p>On a 10+, the MC gives you good detail.</p>
        <p>On a 7–9, the
          MC gives you an impression. If you already know all there is to know,
          the MC will tell you that.</p>
        <p>On a miss, be prepared for the worst.</p>
      </div>

      <div class="hidden" id="help-interfere">
        <p>roll+Hx</p>
        <p>On a 10+, they take +2 (help) or -2 (interfere) to their roll.</p>
        <p>On a 7–9, they take +1 (help) or -1 (interfere) to their roll.</p>
        <p>On a miss, be prepared for the worst.</p>
      </div>

      <div class="hidden" id="lifestyle">
        <p>At the beginning of the session, spend 1- or 2-barter for your
          lifestyle.</p>
        </p>If you can’t or won’t, tell the MC and answer her questions.</p>
        <p>1-barter at the beginning of the session, that’s the
          quality of life her character’s had and can generally expect. The same as
          most people around her.</p>
        <p>2-barter at the beginning of the session, she has a quality
          of life that is substantially, notably better. Whatever other people eat,
          drink, and wear, she’s eating, drinking, and wearing better. She sleeps
          more comfortably and safer, and has more control over her personal
          environment.</p>
        <p>0-barter, this should mean that at the beginning of the session
          she’s desperately hungry and dying of thirst, but hold off. It could turn out
          that way.</p>
        <p>Another PC springs for them;</p>
        <p>If none of them can or will, though, you get to choose:</p>
        <p>• Go straight to, yes, she’s desperately hungry and dying of thirst. Inflict
          harm as established. Take away stuff.</p>
        <p>• Choose a suitable NPC; Now the debt is between her and your NPC.</p>
        <p>• Ask player which NPC You can either negotiate an appropriate arrangement
          in summary, or else jump into play: have her read a person, seduce &
          manipulate, go aggro, or whatever she needs to do to get her way.</p>
      </div>

      <div class="hidden" id="work-gig">
        <p>If you need jingle during a session, tell the MC you’d like to work a gig.</p>
      </div>

      <div class="hidden" id="session-end">
        <p>At the end of every session, choose a character who knows you
          better than they used to. If there’s more than one, choose one at your
          whim. Tell that player to add +1 to their Hx with you on their sheet.
          If this brings them to Hx+4, they reset to Hx+1 (and therefore mark
          experience).</p>
        <p>If no one knows you better, choose a character who
          doesn’t know you as well as they thought, or choose any character at
          your whim. Tell that player to take -1 to their Hx with you on their
          sheet. If this brings them to Hx -3, they reset to Hx=0 (and therefore
          mark experience).</p>
      </div>



    </div>
  </div>

  ...

  <script src="script.js"></script>
</body>

</html>

Any help or a nudge in the right direction would be most appreciated.

The Solution

After staring at this for hours and enlisting the help of a friendly coworker, we finally realized what was going wrong.

The Problem

if (Elements.basicMoves.currentElement === null) {
      Elements.basicMoves.currentElement = newElement;
      Elements.basicMoves.currentElement.classList.toggle("hidden");
    } 
    // #1 This block is the problem!
    else if (Elements.basicMoves.currentElement ===  newElement) {
      Elements.basicMoves.currentElement.classList.toggle("hidden");
    } 
    else {
      // #2 This is where the problem manifests
      Elements.basicMoves.currentElement.classList.toggle("hidden");
      Elements.basicMoves.currentElement = newElement;
      Elements.basicMoves.currentElement.classList.toggle("hidden");
    }

When I toggle a currently displayed element on #1, that element is still stored in Elements.basicMoves.currentElement. When another element is selected #2 goes into effect and toggles the currentElement (causing the previously dismissed element to return), sets the new element to currentElement, then toggles currentElement. The result is that both the dismissed and currently selected elements appear.

The Fix

    if (Elements.basicMoves.currentElement === null) {
      Elements.basicMoves.currentElement = newElement;
      Elements.basicMoves.currentElement.classList.toggle("hidden");
    } 
    else if (Elements.basicMoves.currentElement ===  newElement) {
      Elements.basicMoves.currentElement.classList.toggle("hidden");
      // One line of code to fix!
      Elements.basicMoves.currentElement = null;
    } 
    else {
      Elements.basicMoves.currentElement.classList.toggle("hidden");
      Elements.basicMoves.currentElement = newElement;
      Elements.basicMoves.currentElement.classList.toggle("hidden");
    }

By adding Elements.basicMoves.currentElement = null; to the block that hides the previously selected element, we reset the our state where nothing is displayed, and currentElement is null. This causes the code to function as intended.

The Actual Implementation

While the solution works, my friendly coworker pointed out that having a single button do double duty as a "display" & "clear" button adds unnecessary complexity to the code. So he suggested having two separate buttons which can more clearly indicate their functions and simplify the code.

HTML

      <h2>Basic Moves</h2>
      <select id="basicMovesSelector">...</select>
      <button id="getMove">Show Move</button>
      <button id="clearMove">Clear Move</button>

Javascript

//I added a  clearButton element.
const Elements = {
  ... ,
  basicMoves: {
    selector: document.querySelector("#basicMovesSelector"),
    showButton: document.querySelector("#getMove"),
    clearButton: document.querySelector("#clearMove"),
    currentElement: null
  }
};

/* I added a clearButton event listener, and also changed the event 
listeners to a Arrays instead of an Object.*/
const Listeners = {
  ... ,
  basicMoves: [
    Elements.basicMoves.showButton.addEventListener("click", (e) => { Functions.basicMoves.displayMove(e) }),
    Elements.basicMoves.clearButton.addEventListener("click", (e) => { Functions.basicMoves.clearMove(e) })
  ]
};


const Functions = {
  ... ,
// I broke up basicMoves into two functions. 
  basicMoves: {
    displayMove: (e) => {
      e.preventDefault();

      const newElement = document.querySelector(`#${Elements.basicMoves.selector.value}`);

      if (Elements.basicMoves.currentElement === null) {
        Elements.basicMoves.currentElement = newElement;
        Elements.basicMoves.currentElement.classList.toggle("hidden");
      }
      else {
        Elements.basicMoves.currentElement.classList.toggle("hidden");
        Elements.basicMoves.currentElement = newElement;
        Elements.basicMoves.currentElement.classList.toggle("hidden");
      }
    },
    clearMove: (e) => {
      e.preventDefault();

      /* He showed me this nifty operater currentElement? that will 
check if the value of currentElement is null and skip the rest of the 
line if it is instead of having another if-else block. I was going to 
use a ternary operator but this is so much cleaner.*/
      Elements.basicMoves.currentElement?.classList.toggle("hidden");
      Elements.basicMoves.currentElement = null;
    }
  }
};

Via Active questions tagged javascript - Stack Overflow https://ift.tt/3dmnzlp

Comments

Popular posts from this blog

How to show number of registered users in Laravel based on usertype?

i'm trying to display data from the database in the admin dashboard i used this: <?php use Illuminate\Support\Facades\DB; $users = DB::table('users')->count(); echo $users; ?> and i have successfully get the correct data from the database but what if i want to display a specific data for example in this user table there is "usertype" that specify if the user is normal user or admin i want to user the same code above but to display a specific usertype i tried this: <?php use Illuminate\Support\Facades\DB; $users = DB::table('users')->count()->WHERE usertype =admin; echo $users; ?> but it didn't work, what am i doing wrong? source https://stackoverflow.com/questions/68199726/how-to-show-number-of-registered-users-in-laravel-based-on-usertype

Why is my reports service not connecting?

I am trying to pull some data from a Postgres database using Node.js and node-postures but I can't figure out why my service isn't connecting. my routes/index.js file: const express = require('express'); const router = express.Router(); const ordersCountController = require('../controllers/ordersCountController'); const ordersController = require('../controllers/ordersController'); const weeklyReportsController = require('../controllers/weeklyReportsController'); router.get('/orders_count', ordersCountController); router.get('/orders', ordersController); router.get('/weekly_reports', weeklyReportsController); module.exports = router; My controllers/weeklyReportsController.js file: const weeklyReportsService = require('../services/weeklyReportsService'); const weeklyReportsController = async (req, res) => { try { const data = await weeklyReportsService; res.json({data}) console...

How to split a rinex file if I need 24 hours data

Trying to divide rinex file using the command gfzrnx but getting this error. While doing that getting this error msg 'gfzrnx' is not recognized as an internal or external command Trying to split rinex file using the command gfzrnx. also install'gfzrnx'. my doubt is I need to run this program in 'gfzrnx' or in 'cmdprompt'. I am expecting a rinex file with 24 hrs or 1 day data.I Have 48 hrs data in RINEX format. Please help me to solve this issue. source https://stackoverflow.com/questions/75385367/how-to-split-a-rinex-file-if-i-need-24-hours-data