Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

I would like to access Twine 2 harlowe variables with Javascript.

I have an application where I am embedding a Twine story in an iFrame on a web page and I want to be able to access the variables that get set with the native Twine macros through Javascript. So say I set an variable called $test. How do I get teh value of $test into a Javascript variable outside of the iFrame?

Comments

  • WARNING: Harlowe has no documented Javascript API and is deliberately designed/implemented in a way that restricts access to it's internals.

    The following solution uses an undocumented feature which may not exist in future releases of Harlowe, it uses a Scope Escalation hack to make that undocumented feature available globally, and it contains no error checking so you need to add your own.

    Use the following solution at your own risk.

    The following is broken into two parts:

    1. Making Harlowe's variable State available globally.

    a. You can use the undocumented State.variables property object to access variables via Javascript within a Passage like so:
    (set: $var to 100)
    
    var: $var
    var: (print: State.variables['var'])
    
    ... but the State property only has a Local Scope which means that while Harlowe currently supports you accessing it internally from Passages and the Story Javascript area, it does not support accessing it externally via things like the Developer Console (or an iFrame container)

    b. You can use a Scope Escalation hack to make Scope available globally, the following code is placed within your Story Javascript area. It defines a new global property named harlowe which contains access to the State property.
    if (!window.harlowe){
    	window.harlowe = {"State": State};
    }
    
    ... you should now be able to access the State property within the Developer Console using code like the following:
    harlowe.State.variables['var']
    

    2. Accessing Harlowe's variable State from an iFrame container.
    note: The twine Story HTML file and the HTML file containing the iFrame need to be hosted on the same domain.

    The following is a skeleton of a HTML file containing the Javascript needed to access the internal window object of an iFrame, and via that the escalated Harlowe State object. It assumes that the Story HTML file is named story.html
    <!DOCTYPE html>
    <html>
    <head>
    	<title>Accessing Harlowe's State from iFrame</title>
    	<script>
    		function updateVar() {
    			// Get the window object of the HTML document contained within the iFrame.
    			var iWindow = document.getElementById("frame").contentWindow;
    
    			var inp = document.getElementById("var");
    
    			// Assign the value of the Harlowe 'var' variable to the input field.
    			inp.value = iWindow.harlowe.State.variables['var'];
    		}
    	</script>
    </head>
    <body>
    	<iframe src="story.html" id="frame"></iframe>
    	<br><br>
    	<label for="var">Var:</label><input id="var" type="text"></input>
    	<br><br>
    	<button onclick="updateVar();">Update Var Input</button>
    </body>
    </html>
    
  • Thanks so much for this. Would this be easier using one of the other formats?
  • maranr wrote: »
    Would this be easier using one of the other formats?
    If you look SugarCube's documentation you will see that it has a well documented and full featured Javascript API, it also supports external access of it's internals for DEBUGGING purposes via the SugarCube global property.

    eg. Based on the passage example from 1a. you would use the following command in the Developer Console to access the externally access the value of $var
    SugarCube.State.variables['var']
    

    If you do decide to change to SugarCube then know that TheMadExile (the developer) recommends using the 2.x series as it is the one that is being actively updated/extended, where as the 1.x series mostly only receives bug fixes.
  • Ideally. If you're going to be sending data between a parent window and a child <iframe>, then you really should be using window.postMessage(). There are a couple of issues with direct access.
  • Ideally. If you're going to be sending data between a parent window and a child <iframe>, then you really should be using window.postMessage(). There are a couple of issues with direct access.
    I must admit I did assume that the OP only wanted to retrieve data from the iFrame, not send data to it.
  • edited May 2016
    greyelf wrote: »
    I must admit I did assume that the OP only wanted to retrieve data from the iFrame, not send data to it.
    I did not mention sending data to the <iframe>.

    While code within the <iframe> could be setup to respond to messages from the parent (i.e. parent posts a message requesting the value of $test, child responds with the value of $test), that is not the only way to communicate.

    There's no reason the code running within the <iframe> could not simply push data at the parent at predefined points (e.g. when passage navigation happens or simply at regular intervals).

    There are two major issues with directly accessing the contents of the <iframe> from its parent:
    • Direct access attempts from the parent to the contents of the <iframe> are subject to the same-origin policy.
    • Each window instance (parent and <iframe>) has its own JavaScript host, which do not share their types (e.g. parent Array is not <iframe> Array).
    maranr hasn't given enough information for us to know if either would be an issue.

    Assuming that they're doing something very simple, they may not need to use postMessage() at all—that's why I used the words "Ideally" and "should" in my original post, rather than "must".
Sign In or Register to comment.