[Twine 2 - Sugarcube 2.29] 'getElementById' returns null

I am new to twine, and have very little experience with html/javascript. I have been searching all day but no solutions that seem to be working for others is working for me. I must be missing something or doing something wrong. If anyone can help that would be great. Here is some of my code.

This is in the Story JavaScript

setup.roll = function () {
	var x = document.getElementsByClassName('gdice16')[0];
  if (x.style.visibility === "visible") {
    x.style.visibility = "collapse";
  } else {
    x.style.visibility = "visible";
  }
};

Here is a snippet of my passage. (removed img src links)

src="/greendice11.png" class="gdice11">
      <img src="/greendice12.png" class="gdice12">
      <img src="/greendice13.png" class="gdice13">
      <img src="/greendice14.png" class="gdice14">
      <img src="/greendice15.png" class="gdice15">
      <img src="/greendice16.png" class="gdice16">

Then I have this is my passage.

<<button "Test">><<script>>setup.roll();<</script>><</button>>

My Story Stylesheet has a bunch of images that im using, but here is a snippet.

.gdice11 {
  position: absolute;
  left: 164px;
  top: 226px;
  visibility: visible;
}
.gdice12 {
  position: absolute;
  left: 164px;
  top: 226px;
  visibility: visible;
}
.gdice13 {
  position: absolute;
  left: 164px;
  top: 226px;
  visibility: visible;
}
.gdice14 {
  position: absolute;
  left: 164px;
  top: 226px;
  visibility: visible;
}
.gdice15 {
  position: absolute;
  left: 164px;
  top: 226px;
  visibility: visible;
}
.gdice16 {
  position: absolute;
  left: 164px;
  top: 226px;
  visibility: visible;
}

I should also mention that the div elements are structured as such:

<div class="dicetree">
 <img src="/dicetable.png" class="dicetable1">
<div class="dice1>
      <img src="/greendice11.png" class="gdice11">
      <img src="/greendice12.png" class="gdice12">
      <img src="/greendice13.png" class="gdice13">
      <img src="/greendice14.png" class="gdice14">
      <img src="/greendice15.png" class="gdice15">
      <img src="/greendice16.png" class="gdice16">
</div>
<div class="dice2>
    similar to dice1
</div>
<div class="dice3>
    similar to dice1
</div>
<div class="dice4>
    similar to dice1
</div>
<div class="dice5>
    similar to dice1
</div>
<div class="dice6>
    similar to dice1
</div>
</div>

I just want to toggle the visibility of each image, either immediately in a passage, or with a button press. Either way is fine, though I’d prefer both. Any info or help is much appreciated. Thank you.

The issue is that the passage is first created “virtually” in a <div> that isn’t part of the document yet, so the “getElementBy...()” methods won’t work normally if you try to use them as the passage is displayed.

The easiest way to deal with that is to use code something like this in your passage:

<<script>>
$(document).one(":passagerender", function (event) {
	setup.roll(event.content);
});
<</script>>

(see the SugarCube “:passagerender” documentation)

And you’d want to modify the setup.roll() function like this:

setup.roll = function (context) {
	var x;
	if (context) {
		x = context.getElementsByClassName('gdice16')[0];
	} else {
		x = document.getElementsByClassName('gdice16')[0];
	}
	if (x.style.visibility === "visible") {
		x.style.visibility = "collapse";
	} else {
		x.style.visibility = "visible";
	}
};

That way the setup.roll() function will be able to access the element when the passage is first rendered.

After the passage is fully displayed you’ll be able to access the elements normally (calling setup.roll() without passing a context to it), such as in a button click or whatever.

Enjoy! :slight_smile:

Thanks so much, i will definitely try this when i get back home. I ended up modifying my code and having a link to the same passage and using variables load the images when needed, which is working for now. but i will try your method since this is more inline with what i want.

No problem.

Also, if you want something a bit shorter, you could change your HTML to this:

	<img src="/greendice11.png" id="gdie11" class="gdice">
	<img src="/greendice12.png" id="gdie12" class="gdice">
	<img src="/greendice13.png" id="gdie13" class="gdice">
	<img src="/greendice14.png" id="gdie14" class="gdice">
	<img src="/greendice15.png" id="gdie15" class="gdice">
	<img src="/greendice16.png" id="gdie16" class="gdice">

and then your CSS would just be:

.gdice {
  position: absolute;
  left: 164px;
  top: 226px;
  visibility: visible;
}

That’s because, while an element’s ID needs to be unique on the page, elements’ classes can be repeated in any you want. So, instead of having multiple identical CSS entries, just make one class in the CSS and use it on all elements that need it. You could still refer to individual elements by ID, using “#gdie11” and the like.

Also, make sure you don’t forget the ending " on lines like: <div class="dice1> That should be like this instead: <div class="dice1">

Anyways, once you’d done that, if you wanted to toggle the visibility of all of the green dice, then you could add a little jQuery into your setup.roll() function to shrink it down to this:

setup.roll = function (context) {
	if (!context) {
		context = document;
	}
	$(context).find(".gdice").toggle();
};

And that would toggle the visibility of each element with the “gdice” class. (See the jQuery .toggle() documentation for details.)

If you wanted to toggle the visibility of a single random die with a button click, you could do:

<<button "Roll green die">>
	<<set _groll = random(11, 16)>>
	<<run $("#gdie" + _groll).toggle()>>
<</button>>

If you wanted to make things even shorter still, rather than typing in all of those <div>s and <img>s in your passage, you could just do a loop and print them out in a loop in your passage. Something like this:

<<set _dieGroups = [
{ name: "green", id: "g", start: 11},
{ name: "blue", id: "b", start: 11},
{ name: "purple", id: "p", start: 11},
{ name: "red", id: "r", start: 11},
{ name: "orange", id: "o", start: 11},
{ name: "white", id: "w", start: 11}
]>>
<<for _idx, _dieGrp range _dieGroups>>
	<<set txt = '<div class="dice' + (_idx + 1) + '">'>>
	<<for _i = _dieGrp.start; _i < _dieGrp.start + 6; _i++>>
		<<set txt += '<img src="/' + _dieGrp.name + 'dice' + _i + '.png" ' + 'id="'>>
		<<set txt += _dieGrp.id + 'die' + _i + '" class="' + _dieGrp.id + 'dice">'>>
	<</for>>
	<<print txt + '</div>'>>
<</for>>

And that should display all six groups of six dice (assuming I didn’t screw anything up there) using the data in the array of objects stored in the _dieGroups variable.

Finally, to make it shorter still, instead of hiding and showing all of those different dice images, you could just have one image element for each color die, and set the image source appropriately. So in the passage you might have (a longer version of) this:

<div class="dice1">
	<img src="" id="gdie" class="gdice">
</div>
<<button "Roll green die">>
	<<set _groll = random(1, 6)>>
	<<run $("#gdie").attr("src", "/greendice1" + _groll + ".png")>>
<</button>>

And that would make a random roll, and then set the <img> element’s src attribute to the equivalent image. (See the jQuery .attr() method for details.) You’d add other code in the button as well to use the value of that random roll in your game.

Basically, the lesson is, any time you see yourself repeating nearly identical code, there’s likely a shorter solution.

If you have any questions on any of that, feel free to ask.

Enjoy! :slight_smile: