[Snowman] Programatically create a link and make it clickable without passage events

This one’s a bit long, please bear with me. Could you point me to some possible solution to this?

  1. I want to render passage 2 inside passage 1.
  2. I want to generate a clickable link within passage 2 through a function.
  3. The function cannot listen to passage events for the “make link clickable” part.

In Snowman 2.0.3, I want a function that inserts a link in the passage content. The text part can be as simple as this:

window.insertLink = function( text ) { return "<a>" + text + "</a>" }

And then, to use it in a passage:

<%= insertLink( "This is a link!" ) %>

How to make this link clickable? With this forum’s help, I came to this solution. It uses click() and unbind(), and listens to the passage displayed event to work.

window.insertLink = function( text, target ) {
  // THIS PART MAKES THE GAME CLICKABLE
  $(document).on('sm.passage.shown', function () {
    $("#reveal-"+target).unbind().click(function () {
      // HERE WE PUT WHATEVER THE LINK DOES WHEN CLICKED
    } ) 
  });
  // THIS PART PRINTS THE LINK TEXT
  var link;
  link = '<a id="reveal-'+target+'" href="javascript:void(0)">'+text+'</a>';
  return link;
}

This is a simplified version of what I had in The good people.

Where’s the problem? Here: it listens to the passage displayed event. In the code, this is .on('sm.passage.shown'... In Snowman, when you display a passage through standard Twine links or a story.show() function, a sequence of events is fired.

But if passage display ends, and all events fire, and after that you render another passage inside that with story.render(), there is no event to listen to. If the new passage has my insertLink() function, the part that makes the link clickable won’t work.

So. Do you think there’s any simple solution to this? Or perhaps it would take a new Snowman feature?

Thanks!

What about putting the click binding in a script instead of an event? It’s a nuisance because you have to pass a string with javascript code instead of actual javascript code, but other than that I think it works…and it seems like there should be a way around that, but it’s late and I can’t think of one right now.

window.insertLink = function(text, target) {
	let link = '<a0#reveal-'+target+'>'+text+'</a>'
	let script = '<script>$("#reveal-'+target+'").click(function(){' +
		'DO YOUR LINK CLICKED THING HERE' +
		'})<'+'/script>'
	return link + script
}
2 Likes

Oh, you can use setTimeout(fn, 0) here.

window.insertLink = function(text, target) {
	let selector = '#reveal-' + target;
	function setClickHandler() {
		$(selector).click(function(evt) {
			// DO YOUR LINK THING HERE
		})
	}
	setTimeout(setClickHandler, 0)
	return '<a0'+selector+'>'+text+'</a>'
}

JavaScript doesn’t do multithreading even if there are multiple cores available: it uses a single thread and makes everybody take turns. So when you ask it to wait “at least 0 milliseconds” before setting the click handler, it gives everybody else a turn, letting the link be added to the document so we can find it by its unique id.

Brilliant. I don’t know enough about programming to know if it’s an elegant solution or a terrible hack, but it just works.

Thanks so much!

1 Like