How to Activate/Deactivate a link inside a svg?

Twine Version: 2.3.9
Story Format: Sugarcube 2.31.1

Greetings, everyone. I am experimenting with implementing a basic interactive map system in my game, and I am stuck on the following issue.

I would like to deactivate/reactivate passage links on my interactive map, depending on the state of play. For instance, deactivating the link for a locked room, or while a narrative event is going on. I could make as many variations of my map as I need to display, but that would be cumbersome and I seek for an elegant and practical solution.

Here is how I am testing my map:
I have made two passages, Test 1 and Test 2.
Test 1 contains: <<set $id to "rect1">>TEST1<<include [[Map]]>>
Test 2 contains: <<set $id to "rect2">>TEST2<<include [[Map]]>>

My Map passage has two parts. The first one is the svg

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   xmlns:dc="http://purl.org/dc/elements/1.1/"
   xmlns:cc="http://creativecommons.org/ns#"
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
   xmlns:svg="http://www.w3.org/2000/svg"
   xmlns="http://www.w3.org/2000/svg"
   width="100%"
   height="100mm"
   viewBox="0 0 420.0 297.0"
   version="1.1"
   id="SVGRoot"
   sodipodi:docname="test.svg"
   inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
   <defs id="defs10"></defs>
<metadata
     id="metadata13">
    <rdf:RDF>
      <cc:Work
         rdf:about="">
        <dc:format>image/svg+xml</dc:format>
        <dc:type
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
        <dc:title></dc:title>
      </cc:Work>
    </rdf:RDF>
  </metadata>
   <a data-passage="Test 1"><rect id="rect1" height="50" x="-100" y="10" ry="20" width="100" style="fill:#000;fill-rule:evenodd;stroke-width:2;stroke:rgb(255,255,255)"></rect></a>
   <a id="rect1ToRect2" data-passage="Test 2"><rect id="rect2" width="100" height="50" ry="20" x="50" y="10" style="fill:#000;fill-rule:evenodd;stroke-width:2;stroke:rgb(255,255,255)"></rect></a>
    <rect id="rect3" width="100" height="50" ry="20" x="200" y="10" style="fill:#000;fill-rule:evenodd;stroke-width:2;stroke:rgb(255,255,255)"></rect>
    <path id="path76" inkscape:connector-type="polyline" inkscape:connector-curvature="0" inkscape:connection-start="#rect2" inkscape:connection-end="#rect3" style="fill:none;fill-rule:evenodd;stroke:#fff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="M 150,25 H 200"></path>
    <path id="path84" inkscape:connector-type="polyline" inkscape:connector-curvature="0" inkscape:connection-start="#rect1" inkscape:connection-end="#rect52" d="M 0,25 H 50" style="fill:none;fill-rule:evenodd;stroke:#fff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"></path>
</svg>

The second part is a JQuery script inside a <> macro, that changes the color of the current room and deactivates (or rather attempts to deactivate) the link to the “Test 1” while room in room “Test 2”

jQuery(function($) {
  var patternAColor ='f00';
  // Load patterns
  $("#" + State.variables.id).css({ fill: '#'+ patternAColor });
   if (State.variables.id != "rect1") {
  	$('a[data-passage=rect1]').attr('data-passage', '');
	}
});

I also tried to use the following instead of modifying the attribute:

$( 'a[data-passage=rect1]' ).click(function(e) {
    e.preventDefault();
});

Edited as I go through more tests.

This is only a suggestion, which might help you narrow down the problem a little. It looks like your SVG is an entire Inkscape document. If you choose ‘plain SVG’ from the Save As dialog, you get the graphical elements by themselves.

Thanks for tip. No change to the JQuery’s lack of behavior, but I edited the question.

By the way, I tried using an “if” inside the svg, but the rectangle simply refuses to appear.

The problem is that SugarCube renders the passage “virtually” before adding it to the document, so your JavaScript is unable to find what it’s looking for in the document at the time you’re executing it, because it isn’t there yet.

Also, you don’t need to set $id to the passage name, since the link you want to disable should have the same data-passage value as the value you’d get from the passage() function.

Furthermore, neither removing the data-passage attribute nor doing .preventDefault() will work to prevent link clicks, because SugarCube sets its own (non-default) click handler up before you could remove the attribute.

Thus, the JavaScript you need in your Map passage should look like this:

<<script>>
	$(document).one(":passagerender", function (event) {
		var psg = $(event.content).find('a[data-passage="' + passage() + '"]');
		if (psg.length) {
			psg.css({ cursor: "inherit" }).off("click");
			psg.find("rect").css("fill", "#f00");
		};
	});
<</script>>

That uses the SugarCube :passagerender event to get access to the passage (via event.content) before it’s rendered to the document. Then, if it finds a link to the current passage, it changes the link’s cursor to the inherited cursor (instead of the hand), prevents the “click” event on it, and adds a fill color to the <rect> that’s inside that link. (See also the jQuery methods .one(), .find(), .css(), and .off().)

Now you can just do <<inherit "Map">> without having to set $id at all.

Enjoy! :grinning:

1 Like

Thank you for all the tips. In the meantime, I actually found another solution that achieves my current objective. I will present it here before integrating your own code.

Here are the declaration of the rectangles in the svg:

<a id="toTest1"><rect id="rect1" height="50" x="-100" y="10" ry="20" width="100" style="fill:#000;fill-rule:evenodd;stroke-width:2;stroke:rgb(255,255,255)"></rect></a>
   <a id="toTest2"><rect id="rect2" width="100" height="50" ry="20" x="50" y="10" style="fill:#000;fill-rule:evenodd;stroke-width:2;stroke:rgb(255,255,255)"></rect></a>

Here is the JQuery snippet:

jQuery(function($) {
  var patternAColor ='f00';
  // Load patterns
  $("#" + State.variables.id).css({ fill: '#'+ patternAColor });
  
  if (State.variables.id == "rect1") {
  	$( "#toTest1" ).ariaClick(function (event) {
		event.preventDefault();
  });
  } else {
	  $("#toTest1").ariaClick(function (event) {
      Engine.play("Test 1");
    });
}
if (State.variables.id == "rect2") {
  	$( "#toTest2" ).ariaClick(function (event) {
		event.preventDefault();
	});
} else {
  $("#toTest2").ariaClick(function (event) {
    Engine.play("Test 2");
  });
}
});

Obviously, the raw values are only there for testing purposes.

Once I have tested your solution, I will close the ticket. Many thanks :slight_smile:

Edit:
It works fine :+1:
There are other conditions I might use (like offing the passages that aren’t reachable from the current position), but this is already a very good start.