Access Squiffy from external javascript

For my story, I want to access some of the squiffy functions from external javascript.

I'm thinking this can't be done without modifying story.js because I think the jquery plugin is all private.
What I'm after is the get(attribute) function and the squiffy.story.id.

I'm trying to make a fixed-position div in the main html page that can house an up-to-date inventory.


Last time someone asked for help with a fixed-position div for the inventory, my suggestion was that when they gain or lose an item, they could use jquery functions to change the contents of the fixed div. As this happens in whatever section gives or removes an item, you can easily use the squiffy functions.

The alternative would be having a section starting with something like:

    window.sq = squiffy;

The window object is the global object in javascript, so giving it a reference to a local object makes it accessible elsewhere. I didn't use window.squiffy because you can sometimes have odd behaviour if the window object has a property whose name is the same as the ID of an element (I think this is no longer the issue it used to be, but could be browser-dependant. Easier to be safe).

Including that line in the master section, or in the first section, allows me to type stuff like sq.story.go("whatever"); in the javascript console when debugging, so you should have no problem using it from within javascript elsewhere on the page.


Whoah! I never thought of defining the alias in the master section. I tried all sorts of ways to accessing squiffy in the console and with jquery.

That works like a charm and is just what I wanted!

I typed sq.story.id and got the id in the console just like I wanted.

(I've searched this forum over and over for any and all clues about external javascript, by the way; I've not come across your post. Though, I admit I've only gone back about 6 pages of the forum.)

THANK YOU!

Now, I did figure this out:
I put this in my master section:

$("#inventoryarea").html(squiffy.ui.processText(squiffy.story.sections['inventory'].text))

And it would output a squiffy section in its full glory to my external div.
But I much prefer coding everything in external javascript for this, and leaving my squiffy a bit cleaner.


Wait, that works in the console after you hit the first section; before the first section is passed sq is undefined.

Also, if I write console.log("Story ID=" + sq.story.id); in an external javascript, it is also undefined., even though in the javascript console, I can just write sq.story.id.

For example, even in the console, if you define sq in the master section, if you refresh the page, it comes up undefined. It becomes defined only afer a restart or a new loading.

When in the page cycle will sq become available?


The problem is that sq is undefined unless you restart the game. A page refresh destroys it.

Is there a way to make sq always available?


The problem is that sq is undefined unless you restart the game. A page refresh destroys it.

Script is only run when a section (or passage) is visited. On reloading a saved game, only the HTML content of the page is restored; so any modifications to the squiffy functions won't be updated either.

My only thought is that it might be possible to include a <script> section in the HTML itself; but that wouldn't allow you to access the squiffy object. On the other hand, restoring the HTML can't restore its context. So any links that are already in the output HTML must be able to function without events attached to them.

Alternatively, it seems that you might be able to work out a way around it using transitions. If the Squiffy attribute _transition is set to a string of javascript which, when evaluated, returns a JS function reference, that function will be run immediately after reloading the game. I'm not sure if this code will have access to the squiffy object or not.


Okay, I found access to squiffy like this:

	$(document).ready(function () {
		//a simple .click function won't work for dynamically added items.
		$('#squiffy').on('click', '.squiffy-link:not(".disabled")', function (e) {

		console.log("clicked on " + this.innerText);
			//now perform the one we want
			if (typeof (sq) !== "undefined") {
				console.log("Story ID=" + sq.story.id);
				getinventoryitem("myattribute");
			}
		});
	});

That works if I place this in the master OR in the first section

window.sq = squiffy;

Using the click function like that allows it to gather the information after the dynamically created section or passage is generated by story.js. The attributes only change after a link is clicked, so sq is always defined during those times because it's fired in the master section.

However, now I have a new problem - the attributes in the section only update after the link has been clicked, and so my inventory gotten via sq.get("myattribute") is always behind. If I change the value in the section or passage, I can't read the current value - I only have the former value. If I place a settimeout delay within that click function, it just makes the click even take longer and the squiffy story just takes longer to advance. I guess I need an async delay to fetch the value.


Ahhh! Now this works:

	$(document).ready(function () {
		//a simple .click function won't work for dynamically added items.
		$('#squiffy').on('click', '.squiffy-link:not(".disabled")', function (e) {

		console.log("clicked on " + this.innerText);
			//now perform the one we want
			if (typeof (sq) !== "undefined") {
				console.log("Story ID=" + sq.story.id);
				setTimeout(function () {
					getinventoryitem("myattribute");
				}, 100);
			}
		});
	});

OK… skimming the code here.

It looks like the function squiffy.ui.transition() takes as its parameter a javascript function. That function will be run immediately. It will also be run if the window is closed and reloaded before the next section link is clicked.

So you could use the transition function to set up anything that needs to be run when the game is loaded; but it's cleared whenever you visit a new section, so you'd have to modify some of the code to set it again if you want it to stay set.

I'm thinking something like:

[[first section]]:
    (squiffy.story.sections['onload'].js)();

[[onload]]:
    if (!window.sq) {
      window.sq = squiffy;
      squiffy.story.save = function() {
        squiffy.set('_output', squiffy.ui.output.html());
        if (!squiffy.get('_transition')) {
          squiffy.set('_transition', "squiffy.story.sections['onload'].js");
        }
      };

      // Any code you put here will be run once at the start of the game, and again when the
      //   game is reloaded so you can use it to define functions and stuff, or anything else
      //   that you want to be done as soon as the `sq` variable is available
    }

That way you don't have to wait for the player to click on something (and it means that your code only needs to run once)

I'm typing on my phone, so I haven't tested this yet. But after a quick look through the code, I think it should be usable.


I dunno. I can't seem to access any external js functions from within squiffy.

Trying your code above doesn't make sq defined for a page refresh.

I came up with this nice routine that I run in the external js to loop through all the localstorage and filter out the inventory items. This way I don't need to manually code them all. It will find any attribute that starts with -inv_. So if I name my attributes in squiffy like:

{@inv_Daggers=5}

Then I can just grab the items that should be tracked in my inventory and ignore all the flags. And I don't have to keep a list of attribute names anywhere.

But this is in the external js. I can run it fine when I click on squiffy links, but I can't keep the inventory div populated between refreshes, and I can't call that code from squiffy, even if I put it in your onload setup. I hate to put so much javascript in squiffy like that.

function getinventory(whatsquiffy){
	
	//first, add a life counter with a special style
	var thisinventory = "<span class='inventory-life'>Life: " + whatsquiffy.get("life") + "</span><ul>"; 
	
	
	//loop through all the keys in localstorage to find our attributes with -inv_ before their name
	var archive = [],
		keys = Object.keys(localStorage),
		i = 0, key;

	for (; key = keys[i]; i++) {
		var lenprefix = whatsquiffy.story.id.length + 5;	// keyname should be like this:  41c01670ee-inv_corn=4
		var thisvalue = localStorage.getItem(key);
		//tst to make sure we are using our special inventory attribute names and that the value is not zero
		if( (left(key, lenprefix) == whatsquiffy.story.id + "-inv_" ) && thisvalue != "0"){
			var attributename = key.split("-inv_");		//fetch the part after the -inv_
			attributename = attributename[1].replace("_", " ");  //we are using _ in the key name to represent spaces for the attribute name
			thisinventory += "<li>" + attributename + ": " + thisvalue + "</li>";
			//archive.push( key + '=' + localStorage.getItem(key));
		}else{
			continue;
		}
	}
		
	thisinventory += "</ul>";
	return thisinventory;
}

What happens is that on page refresh, sq is not defined until the user clicks on a squiffy link.

Now, the restart brings everything to life. This is the restart function that comes in the default html output. Now, THAT seems to be able to call functions from the squiffy plugin, even if sq isn't loaded.

		$('#squiffy').squiffy();
		var restart = function () {
			$('#squiffy').squiffy('restart');
		};
		$('#restart').click(restart);
		$('#restart').keypress(function (e) {
			if (e.which !== 13) return;
			restart();
		});

Maybe there is some function like restart that will do the same thing without having any effect? I tried using

$('#squiffy').squiffy('load');

but it doesn't seem to recognize that command.


Okay, none of that worked, so I hardcoded the squiffy story id into my javascript. That's really what I needed in the end. With that, I can make my inventory system entirely external to squiffy, loading the attributes from local storage.


Maybe there is some function like restart that will do the same thing without having any effect? I tried using

I'm not sure what you're trying to do there. load is automatically called by Squiffy itself on startup if there are attributes for this game in localStorage.
The only methods you can call using the $('#squiffy').squiffy() syntax are "init", "get", "set", and "restart". ('init' is responsible for calling initLinkHandler and a couple of housekeeping functions; and I see no point in calling this externally)


OK, I've properly tested the code I suggested before, and confirmed that it works.

Here's a simplified version:
In your external JS, you'd want to define a callback to be run after the squiffy object is available (use this instead of $(document).ready()):

$('#squiffy').on('sqready', function () {
  // all the script here to do something with sq
});

and in the first section of your game, the javascript to make the squiffy object available:

    // make the object available
    window.sq = squiffy;

    // notify the external JS that it's ready:
    $('#squiffy').trigger('sqready');

    // and modify how the save function works, so that this script
    //   will be run again when reloading a saved game
    var initsection = squiffy.get('_section');
    squiffy.story.save = function () {
        squiffy.set('_output', squiffy.ui.output.html());
        squiffy.set('_transition', "squiffy.story.sections['" + initsection + "'].js");
    };

Hmmm... It doesn't seem to be firing. I put the jquery in my page head and placed a function call there (simple alert). And I put the lower part in the game like you said. Nothing. It won't even log to the console.

It doesn't fire on refresh or restart. So I tried putting it in document.ready, and that doesn't work either. There's no javascript errors, though.

HTML's javascript:

		<script>
			$('#squiffy').on('sqready', function () {
			  // all the script here to do something with sq
			  test();
			});		
		
			function test(){
				alert("bob");
			}
		
			jQuery(function($){
				$('#squiffy').squiffy();
				var restart = function () {
					$('#squiffy').squiffy('restart');
				};
				$('#restart').click(restart);
				$('#restart').keypress(function (e) {
					if (e.which !== 13) return;
					restart();
				});
			});
		</script>

Squiffy Code:

 @title JS Test
 @start init

[[init]]:
// make the object available
window.sq = squiffy;

// notify the external JS that it's ready:
$('#squiffy').trigger('sqready');

// and modify how the save function works, so that this script
//   will be run again when reloading a saved game
var initsection = squiffy.get('_section');
squiffy.story.save = function () {
    squiffy.set('_output', squiffy.ui.output.html());
    squiffy.set('_transition', "squiffy.story.sections['" + initsection + "'].js");
};
//squiffy.story.go("first");

[[first]]:
Halleluia!

Log in to post a reply.

Support

Forums