How to automatically add objects from a list into a table?

I plan to have a store in the game where the player can buy different kinds of equipments, and to make it easier in case I want to add or remove equipment in the future, I wanted to make it so they get added automatically from a list. So this is the code I wrote:

<table>
  <tr>
    <th>Weapon</th>
    <th>Damage</th>
    <th>Price</th>
    <th>Action</th>
  </tr>
  <<for _item range $melee_weapons>>
      <tr>
        <td>_item.name</td>
        <td>_item.damage[0] to _item.damage[1]</td>
        <td>_item.price</td>
        <<if $player.money > _item.price>>
          <td>
            <<link "Buy">>
              <<set $player.money -= _item.price>>
              <<add_to_inv _item>>
            <</link>>
          </td>
        <</if>>
      </tr>
  <</for>>
</table>

This doesn’t actually work, and I’m not sure why, it ends up looking like this:

Is there a way to fix it, or maybe even a smarter way to achieve what I’m trying to do? Or should I just bite the bullet and add it manually every time instead of trying to be smartass?

There are two main issues with your current implementation:

1: You’re not suppressing the generation of <br> line-break elements.

By default any line-break in your Passage content is automatically converted into a HTML <br> element, and HTML has rules about where such can be placed within a structure like a HTML “table”. And the visual outcome you are seeing is what can happen when the web-browser tries to “fix” <br> elements being in the wrong place.

Simply wrapping your “table” structure with a <<nobr>> macro will fix this issue.

2: You’re trying to access the _item.price property after the Passage has finished being processed.

Code within the body of an interactive component, like that generated by the <<link>> in your example, isn’t executed until the end-user selects that component.

This means that the _item temporary variable created by the <<for>> macro may not longer exist, or if it does exist it could have a different value then it had when a specific <<link>> macro was called.

The <<capture>> macro can be used to overcome this issue.

The following an updated variation of your own example with the above added to it…

<<nobr>>
<table>
  <tr>
    <th>Weapon</th>
    <th>Damage</th>
    <th>Price</th>
    <th>Action</th>
  </tr>
  <<for _item range $melee_weapons>>
      <tr>
        <td>_item.name</td>
        <td>_item.damage[0] to _item.damage[1]</td>
        <td>_item.price</td>
        <<if $player.money > _item.price>>
          <td>
          	<<capture _item>>
            	<<link "Buy">>
					<<set $player.money -= _item.price>>
					<<add_to_inv _item>>
				<</link>>
			<</capture>>
          </td>
        <</if>>
      </tr>
  <</for>>
</table>
<</nobr>>
1 Like

That didn’t seem to be enough to fix the table (but thank you for the warning about the capture macro!), it still looks the same as in the original screenshot.

That’s a perfectly reasonable thing to do. You’re not attempting anything weird.

 

That looks like the entire table contents, after the record containing the headers, is being dumped into one data element. That’s obviously not what should be happening here.

Is what you showed the entirety of the code in that passage or did you elide some? Also, are you using any table styles?

 

It’s been a while since I wrote a loop generated table, but the example @Greyelf gave should mostly work.

I would suggest a couple of minor changes though:

  1. I’d move the <<if>> within its <td>, so the element always exists. Empty table data elements are okay, missing ones not so much.
  2. I’d change the money ↔︎ price comparison to use greater-than or equal (>=), since having exactly enough money should work as well as more than enough.

Additionally. As long as you’re using a version of SugarCube ≥v2.31.0, then you shouldn’t need to wrap the table within any no-break feature. You’re probably using v2.36.1, so you’re good without the <<nobr>>. It shouldn’t hurt anything to use, it’s simply no longer necessary.

For example: (untested at the moment)

<table>
	<tr>
		<th>Weapon</th>
		<th>Damage</th>
		<th>Price</th>
		<th>Action</th>
	</tr>
	<<for _item range $melee_weapons>>
		<tr>
			<td>_item.name</td>
			<td>_item.damage[0] to _item.damage[1]</td>
			<td>_item.price</td>
			<td>
				<<if $player.money >= _item.price>>
					<<capture _item>>
						<<link "Buy">>
							<<set $player.money -= _item.price>>
							<<add_to_inv _item>>
						<</link>>
					<</capture>>
				<</if>>
			</td>
		</tr>
	<</for>>
</table>
1 Like

FYI. That hasn’t been required with SugarCube’s HTML parser since v2.31.0. Not that using no-break should hurt anything.

From the v2.31.0 change log:

  • Updated the HTML parser to ignore line-breaks where appropriate. E.g., removes the need to use a line-break control feature within <table> tags.
1 Like

Is what you showed the entirety of the code in that passage or did you elide some? Also, are you using any table styles?

This is the all the content of the melee store passage (except for a [[Return|$return]] after the table that I ommited) and there’s nothing in there that I added from somewhere, I am using this for table

table, th, td {
  border: 2px solid black;
}

Thank you for the suggestions! I didn’t know a missing td was bad, and the money comparison was a huge oversight on my part.