Variable Conversion (Twine to JavaScript)

I’m sure this is easy, and it’s a newbie question, but I don’t see a section that deals with it in the reference documentation.

I’d like to be able to pass a variable value in Twine to a javascript variable, or vice versa. How do I do that?

If I use the <<script>> mechanism, the ref docs suggest that I have to write pure javascript code, which won’t know about my Twine variable names, right?

$whatever is just a shortcut for State.variables.whatever or variables().whatever. You can also access it with State.getVar("whatever") and State.setVar("whatever"). Whichever way suits you best.

A handy trick you can do is use a getter, like so:

Object.defineProperty(window, "v", { get: function() { return State.variables; } })

Using that, you can access it via v.whatever. It’s really convenient.

Honestly, I’m not trying to be obtuse. I know several programming languages. I have a game in this year’s comp written in TADS 3. But what you’re saying does not link up for me. I don’t understand what you’re saying.

When you say, “use a getter,” for instance. What’s a getter? Is the State object a javascript object, or is it something in Twine? Where would I put any of this code?

If I’ve defined something in a Twine Passage, such as <<set _my_var to 3>> (or “rusty dagger” or whatever) and I then want to access the current value of _my_var by sending it off to my javascript for some fancy processing, how would I do that? And how would I get the value back into the Twine Passage after my javascript code has done its dance?

This is a getter: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get

But you don’t really need to know how it works. Just copy that code I pasted directly and you’ll be able to access your story variables via v.story_variable_name.

The State object is a SugarCube object. It represents the current state of the story (IOW, the current variables are different than the ones in your history). You can find more info about it here: https://www.motoslave.net/sugarcube/2/docs/#state-api-getter-variables

For temporary variables (the _whatever variables), you access them with State.temporary instead. https://www.motoslave.net/sugarcube/2/docs/#state-api-getter-temporary

I messed up with the getVar/setVar parts though. It’s been a while since I’ve used Twine, so I forgot that you need to pass it the leading tokens (or whatever it’s called) along with the name. This is how you can access temporary variables with it. So State.getVar("$story_var") and State.setVar("_temp_var").

As for where you would put this code:

If you choose to use the getter, you would put that bit of code in your StoryInit passage. It’s a “set it and forget it” function that only needs to be run once.

As for the other stuff, that all goes in your <<script>> sections which runs pure javascript (meaning it doesn’t accept story variable names like $whatever, as $ is reserved for jquery). If you only need to run a quick command, however, you can just use <<set>> or <<run>>. Those two do accept story variable names.

Sorry – I’m still befuddled. What I would love to read would be a good, thorough tutorial, with step-by-step code examples, showing how to integrate javascript code into a Twine project so as to accomplish ten or twenty basic things.

Is there such a tutorial? I’ve been looking, and I haven’t found one.

Possibly I could explain what I’m trying to do. It’s a very primitive and silly experiment, which I’m performing in order to try to learn how this stuff works. I have a passage with three clickable words for inventory items that the player can acquire by clicking on the words. I then want to have a clickable Show Inventory command that will reveal whatever the player has just clicked on. The problem I’m trying to solve is that my first attempt at a Show Inventory is evaluated early, when the array containing possible inventory items is still empty. So I figured, okay, I need to pass information over to javascript when the player clicks on “dagger” or “lantern” and then bring it back using a script, so that when my Show Inventory command is invoked, the correct data will be available. Now, I don’t know that this will work either! Maybe the javascript will be evaluated early too. I’m just trying to solve a simple coding problem, that’s all. If using javascript doesn’t solve this problem, it will still be useful, I’m sure, in a general way, to know how to toss values back and forth.

The SugarCube reference documentation is almost entirely opaque. It never really explains anything. I’m sitting here with the O’Reilly JavaScript book at my elbow – the 2011 edition, but I’m sure it’s almost completely current, because why would anything in the basic code syntax break? I can understand what’s in the book, because things are explained. But getting from javascript to Twine and back – naturally, the book says nothing about that. I really need to read a tutorial of equivalent prolixity.

Your suggestion that I put that code in my StoryInit (even if I had one) wouldn’t solve this particular problem, because I don’t want something set up at init time. I want it evaluated only when the user clicks on a word in a particular passage.

You might want to take a look at this, if you haven’t already. I don’t think I can link directly the “examples” tab, but that’s where I’m trying to direct you. https://twinery.org/cookbook/

Sadly, that probably is the best site for examples of simple things. It’s supposed to be a comparison of the languages, but it’s also the closest you’ll get to basic examples, as far as I know.

You’ll want to have one at some point. StoryInit is a special passage that’s called before all other story passages. It comes after all javascript passages are loaded and before Start is run. Unlike Start, StoryInit is called even when you load a save, so it’s an important passage to dump stuff into that you need to have run for saved games as well as new games.

As for your code issue, <<script>> will probably be your best best, placed inside a <<link>> tag, maybe.

An alternative is to use a widget, which is basically like a mini passage called like a function and you can pass arguments to. What’s convenient about widgets is that they’re written like normal story script (IOW, it’s all HTML and sugarcube tags and accepts story variable names).

You can find more widget info here: https://www.motoslave.net/sugarcube/2/docs/#macros-macro-widget

I’ve been to the cookbook. The page on using javascript is nothing but a stub. I just now watched a completely useless video, in which the only javascript example given was how to pop up a browser dialog box before the Start Passage loads. I mean, wow. Seriously. Wow.

I’ve been trying to use <<script>> inside a <<link>>. I managed to figure that out. But the business of passing variables back and forth (and whether that will even solve the very silly problem I set for myself) is unknown. I thought of using a widget too. I don’t remember what happened.

BTW, your hashtagged links (such as the one above) are defective in Firefox. I don’t know why.

Oh weird. The link works just fine in chrome.

Anyway, I got some code working that I think may be what you want. I even wrote it two different ways.

First, put this in your javascript passage:

variables().items = [];

(Or you could put <<set $items = []>> in your StoryInit passage. Whichever.)

The following is how I would do it using javascript:

<<link "Buy Apple">><<run $items.push("Apple")>><</link>>
<<link "Buy Banana">><<run $items.push("Banana")>><</link>>
<<link "Buy Orange">><<run $items.push("Orange")>><</link>>

<<nobr>>
<<link "Show Items">>
	<<script>>
		var items = variables().items;
		$("#items").empty();
		for (var i = 0; i < items.length; ++i) {
			$("#items").wiki(items[i] + "<br>");
		}
		$("#items").wiki('<<link "close">><<replace "#items">><</replace>><</link>>');
	<</script>>
<</link>>
<</nobr>>

<div id="items"></div>

But JS isn’t really necessary since you can just use normal sugarcube tags:

<<link "Buy Apple">><<run $items.push("Apple")>><</link>>
<<link "Buy Banana">><<run $items.push("Banana")>><</link>>
<<link "Buy Orange">><<run $items.push("Orange")>><</link>>

<<nobr>>
<<link "Show Items">>
	<<replace "#items" t8n>>
		<<for _i = 0; _i < $items.length; ++_i>>
			<<= $items[_i]>><br>
		<</for>>
		<<link "close">><<replace "#items" t8n>><</replace>><</link>>
	<</replace>>
<</link>>
<</nobr>>

<div id="items"></div>

Thanks – that works perfectly (and without the JS). I have no idea why there’s a hashtag in the <<replace lines, nor do I understand why there’s a div statement in it. It appears that the replace line is replacing a div with the for loop, or alternatively with nothing (on “close”), but why that works, or why the div statement is floating off at the end of the code, I have no idea.

The fact that I don’t understand what’s going on immediately turns around and bites me again. My next thought was to get rid of the items after they’re displayed. So I tried this:

     <<link "close">><<replace "#items" t8n>>
		<<set _len to $items.length>>
		<<for _i = 0; _i < _len; ++_i>>
		<<$items.pop()>>
		<</for>>

And of course that doesn’t work at all. Logically it ought to work, as far as I can see, so obviously I don’t understand the syntax.

Where would I go to read a tutorial that explains this kind of stuff? This is not quite an idle question. I wrote the I7 Handbook ten years ago, and people are still using it. One of my mottos is, “If you want to learn something, write a book about it.” I could actually be moved to write a book about this type of programming, but first I would need to understand it…

This is the info on <<replace>>: https://www.motoslave.net/sugarcube/2/docs/#macros-macro-replace

The hashtag is targeting the div ID. I probably should have named them different to be more clear. The names aren’t linked or anything. The “t8n” part makes it fade in.

You’re correct that it’s replacing the div. But probably more accurately is that it’s replacing the contents of the div. (It’d be documents.getElementById("#items").innerHTML, or $("#items").html() in jquery, if we’re being super specific.)

The way that <<replace>> works is that it renders its content to a buffer when run and then pastes that into the target HTML element. In this case, we’re collecting all of the items that you have in the inventory, adding a close button to it, and then putting that into the div element below the “show items” link. The close button sets the div’s content to nothing again, as you surmised.

Since the “show items” link calls the replace macro each time, and the replace macro runs the for loop fresh each time, it doesn’t just recycle the old text. If you click an item while the list is still displayed and then click the “show items” link again, it’ll refresh the list with updated inventory.

I hope that makes sense. I’m horrible about explaining things like this.

Hopefully I can clear up a bit of confusion here. If you have data in the Twine story variable $storyVar and the temporary variable _tempVar, and you want to access them within your JavaScript, then you would use State.variables.storyVar and State.temporary.tempVar, respectively.

Also, keep in mind that you can use JavaScript inside of any of the SugarCube macros. This is commonly done within the <<set>> and <<run>> macros (one is an alias of the other). For example, instead of doing this:

<<set $someObject = { key1: "value1", key2: "value2", key3: "value3" }>>
<<script>>
	State.temporary.keys = Object.keys(State.variables.someObject);
<</script>>
_keys

You could do this:

<<set $someObject = { key1: "value1", key2: "value2", key3: "value3" }>>
<<set _keys = Object.keys($someObject)>>
_keys

Those two code snippets do the exact same thing, though the second one is quite a bit shorter.
 

It’s a SugarCube object. See the State API documentation.
 

You should access _my_var within your JavaScript by using State.temporary.my_var, and you could use that to both get and set the value of that variable.
 

Looks like the security certificate still hasn’t quite been fixed there. Just change “https://” to “http://” and they will work fine.
 

It’s not a “hashtag”. It’s a CSS selector, where a hash “#” in front of a name means that it refers to an element’s ID (IDs should be unique on a page), and a period “.” refers to all elements with that class (classes do not need to be unique on a page). So the “#items” refers to the <div> because that <div> has “items” in its id attribute.

That selector tells the <<replace>> macro what its target is for replacing the content within that target. See the <<replace>> macro for details.
 

I’m not sure if it quite meets your criteria, since it’s not entirely meant as a tutorial, but you might want to take a look at my Twine/SugarCube sample code collection. The “Using JavaScript with SugarCube” section helps explain a lot of the things I went over above.

Hope that helps! :grinning:
 


 

Just to be clear, the “Start” passage name is only special in Twine v1 and Twee. It’s not a special passage in Twine v2, since any passage can be set to be the starting passage in Twine v2.

I just want to avoid adding any confusion which could occur by incorrectly implying that a passage named “Start” has any special purpose when used in the current version of Twine.

1 Like

Thank you for that. I almost exclusively use tweego so I forget the small differences sometimes.

Both Firefox and Chrome give me a security warning when I attempt to follow your links. I’m not inclined to want to take the chance. I’d love to read your articles! Could you supply the actual links?

Actually, the real link has the same problem. I used a search engine to find your page, and got this link: https://qjzhvmqlzvoo5lqnrvuhmg-on.drv.tw/UInv/Sample_Code.html. Same deal – Firefox thinks it’s a security risk.

It’s not really a security risk. It’s just Chrome and Firefox being over protective. It’s because it’s an HTTPS address but the certificate expired. It’s like denying someone alcohol because their license expired, as if they’ve de-aged somehow now that the license is no longer valid.

Ironically, if this were a normal HTTP address instead of HTTPS, it wouldn’t require a security certificate and it wouldn’t throw an error. The site would be less secure but you wouldn’t be alerted.

That being said, it sure would be great if the sugarcube docs would update their certificate so it didn’t look like a sketchy phishing site every time I visited.

@Jim_Aikin I somehow missed your issue with your code last night. Probably because the editor on the phone takes up the entire phone screen and you posted it while I was typing. But anyway…

     <<link "close">><<replace "#items" t8n>>
		<<set _len to $items.length>>
		<<for _i = 0; _i < _len; ++_i>>
		<<$items.pop()>>
		<</for>>

If you haven’t figured out your issue, it’s that you’re not using <<run $items.pop()>> or <<set $items.pop()>>. You can’t just dump JS into a sugarcube tag. You have to use set or run to tell sugarcube to execute it.

As HiEv mentioned, set and run are just aliases of the same thing to keep the code more readable. However I’m lazy and I just use set for everything regardless of whether assigning a value or running a function.

That makes perfect sense. I tend to think about code in procedural terms – that when a line is encountered by the software, the line is executed. The idea that you have to tell the software to execute a line in order for the line not to be ignored is a bit foreign to me.

It’d probably make more sense once you realize that everything inside a << >> tag is basically a function in sugarcube’s list of macros (widgets and user defined macros get added to this list) and stuff not listed in sugarcube’s list of macros throws an error. The first word in the << >> tag is the function name and what follows it are the arguments.

The reason <<run $items.pop()>> works is because run() is a function that takes a string argument that it then evaluates (executes). So <<run $items.pop()>> is really saying run("$items.pop()");

Or at least that’s my assumption of the process. I never actually peeked at that part of sugarcube’s source code.

All that security warning means is that it can’t encrypt the data that you receive, which isn’t particularly necessary in this case. It’s not like you’re sending private information or anything.

That said, I’ve contacted DriveToWeb (drv.tw) and they’ve apparently fixed the problem.

Also, if you just want to download all of the files to open it locally, you can download it from here.

Enjoy! :grinning: