0 votes
by (870 points)

I'm currently implementing a party composition mechanic for my RPG. The idea is that the characters in the current party are displayed, and when one is clicked, their block is highlighted to show they are selected. However, I can't seem to figure out how to manipulate the element's classes to accomplish this.

My code currently looks like this:

<<for _i, _puppet range $puppets>>
<div class="actor">
	<center>
	<<capture _i>>
	<<link _puppet.name>>
		<<set _selected = _i>>
		<<script>>
		$(this).parent().addClass("selected");
		<</script>>
	<</link>>
	<</capture>>
	</center><br/>
</div>
<</for>>

From my understanding of jQuery, this should work. However, nothing happens when I click the link. Do I need to <<replace>> the element for it to reload and be rendered correctly? But if so, won't that just overwrite the added class? I'm rather confused here.

The highlighting does appear when I manually add the "selected" class to the div element in the code, so there are no errors on the CSS end.

1 Answer

0 votes
by (159k points)
selected by
 
Best answer

WARNING: For simplicity sake the following information is not 100% techniqually correct, and there are layers of SugarCube specific internal code that have been left out of the explanations.

NOTE: You didn't include an example of the structure of puppets collection, so I will assume that it looks something like the following. (I have only included the referenced name property)

<<set $puppets to [
	{name: "1st Puppet"},
	{name: "2nd Puppet"},
	{name: "3rd Puppet"}
]>>

There are a couple of issues with the logic of your sample.

1. The $(this).parent().addClass("selected"); line is being executed within the context of a script element, which itself is within the context of an 'onclick' event handler function associated with generated a (anchor) element being used to represent the link.

This means that the this operator isn't referencing what you think it is.

2. If you use your web-browser's built-in Web Developer Tool to Inspect the HTML structure of the current generated page while viewing the relevant Passage, you will see that each of your actor classed div elements looks something like the following.

<div class="actor">
	<br>
	<center>
		<br>
		<br>
		<a class="link-internal macro-link" tabindex="0">1st Puppet</a>
		<br>
		<br>
	</center>
	<br>
	<br>
</div>

So even if the this operator did reference the a (anchor) element like you thought it did then the actual parent of that that element would be the center element, and not the classed div element that you want to target with your addClass() function.

One way to solve your issue is to assign each actor classed element a unque ID attribute based on the name of the associated puppet, and then to use this ID to target it which appling the selected class. The following example does this, and it uses the undocumented Util.slugify() function to convert the puppet name part the identifier as well as Attribute Directive markup to inject the generated identifier into the associated div element.

note: I replaced your <<script>> macro with a <<run>> macro because you are only executing a single JavaScript statement, and because it handles accessing story/tempory variables better.

<<for _i, _puppet range $puppets>>
	\<<set _id to 'puppet-' + Util.slugify(_puppet.name)>>
<div class="actor" @id="_id" >
	<center>
	<<capture _i, _id>>
	<<link _puppet.name>>
		<<set _selected = _i>>
		<<run $('#' + _id).addClass("selected")>>
	<</link>>
	<</capture>>
	</center><br/>
</div>
<</for>>


Your original example (as well as the above one) outputs a number of blank lines between each of the links, this is due to the line-breaks within the formatted code automatically being converted to br element in the generated page output. You can use both Line Continuation markup and the <<nobr>> macro to supress those blank lines.

The following demostrates how to do this using a modified version of my above example.

<<nobr>>
<<for _i, _puppet range $puppets>>
	<<set _id to 'puppet-' + Util.slugify(_puppet.name)>>
<div class="actor" @id="_id" >
	<center>
	<<capture _i, _id>>
	<<link _puppet.name>>
		<<set _selected = _i>>
		<<run $('#' + _id).addClass("selected")>>
	<</link>>
	<</capture>>
	</center><br/>
</div>
<</for>>
<</nobr>>


Also because the div element is block based you don't need to include a manual br element after the center element to achieve a fixed gap between each of the actor classed div, this can easily be done by adding a CSS rule in your Story Stylesheet area. The following addes a 1em margin between the divs, it assumes that you have deleted the manual <br/> from the above example.

div.actor {
	margin-bottom: 1em;
}

 

by (870 points)

"One way to solve your issue is to assign each actor classed element a unque ID attribute based on the name of the associated puppet, and then to use this ID to target it which appling the selected class."

Oh, I've been wondering how to do that for ages! That makes this whole thing straightforward, yes. Thank you!

...