Can you disable a (click:) after another (click:) is selected?

Twine Version: 2.5.1
Story Format: Harlowe 3.3.3

Issue:
I want to display two or more options for a user to select between, using (click:) or (link:) macros. After the user clicks one of these options, I’d like for the other one to no longer be clickable. In other words, the user should pick one choice, but not both. I want this to happen within the same page.

Code:
This is a basic example of what I am working with. A simple two-option choice that modifies a variable. The user should only get to pick one.

Which do you like more? Cats or Dogs?

[(click: "Cats") [(set: $cat to it + 1)]]
[(click: "Dogs") [(set: $dog to it + 1)]]

(set: $cat to 0)
(set: $dog to 0)

Testing:
I know this can be accomplished by changing the targeted text inside the (click:) macro, such as the example below, but if possible I would like the text to stay the same and just not be clickable anymore.

<!--Possible, but not what I want.-->
Which do you like more? Cats or Dogs?

[(click: "Cats") [(set: $cat to it + 1)(replace: "Dogs")[Woof]]]
[(click: "Dogs") [(set: $dog to it + 1)(replace: "Cats")[Meow]]]

(set: $cat to 0)
(set: $dog to 0)


<!--Another variation, but causes issues when using style changers.-->
Which do you like more? |bird>[Eagles or Owls?]

[(click: "Eagles") [(set: $eagle to it + 1)(replace: ?bird)[Eagles.]]]
[(click: "Owls") [(set: $owl to it + 1)(replace: ?bird)[Owls.]]]

(set: $eagle to 0)
(set: $owl to 0)

Closing Thoughts:
This issue was brought up in a post called “Disabling (click:) links, without reloading page, without replacing original text (Harlowe)” on the old Twinery forum back in 2017, but never solved. This site won’t let me post links, but if you can find it, maybe their code can help with discovering a potential solution.

Any ideas are appreciated! I don’t know if this is even possible, but there are several instances where I’d like to perform this kind of “one or the other” type choice for the user in my games. It would be a huge help to me if we could find a solution. Thank you!

notes:

  1. Due to how the ‘click’ family of macros need to scan the entire page to locate all occurrences of its target, it is generally recommended to use the ‘link’ family of macro instead whenever possible.
  2. If the situation really does require the usage of a ‘click’ macro to solve it then it is better to target a Named Hook than a String value, because it is quicker (and easier) for the web-browser to find a <tw-hook> element with a specific name than a piece of textual content.

Once a ‘click’ macro has converted its target (Named Hook or Text) into a <tw-link> element there is no way to un-convert it, short of replacing that target with something other than the original target.

Your code variation that replaces a Named Hook with a new value is normally how this situation is handled, except ‘link’ macros would be used instead.

{
(set: $eagle to 0, $owl to 0)

Which do you like more? |birds>[
	(link: 'Eagles')[
		(set: $eagle to it + 1)
		(replace: ?birds)[Eagles.]
	]
	or
	(link: 'Owls') [
		(set: $owl to it + 1)
		(replace: ?birds)[Owls.]
	]
]
}
2 Likes

Hi Greyelf,

Thank you for the advice!

I realize that replacing the text using a Named Hook is a possible solution, but it’s not quite what I’m looking for. Ideally, I don’t want the text to change after the user has made their choice. I only want their ability to click both options to be removed when they select one of them. Is such a thing possible?

Part of the reason I’m being stubborn about this is that text styles or effects (like the ‘t8n-delay’) seem to fire again when using the Named Hook method we talked about.

preamble / warning:
Harlowe has been deliberately designed to limit an Author’s ability to use JavaScript to extend the functionality of their project, or of the story format’s runtime engine itself. That engine has no documented JavaScript API, and only recently was the ability to access Story & Temporary variables from within a standard HTML <script> element adding.

Web-developers normally use JavaScript, and possibly CSS, to dynamically disable the intractability / editability of an element.

The following describes one possible solution for doing the same with a Harlowe 3.x based project, however the solution has not been thoroughly tested so it may not work for all combinations of Web-browser & Operating System & Device Type.

If you add the following to a Passage…

{
(set: $eagle to 0, $owl to 0)

Which do you like more? |birds>[
	(link-reveal: 'Eagles')[
		(set: $eagle to it + 1)
	]
	or
	(link-reveal: 'Owls') [
		(set: $owl to it + 1)
	]
]
}

…and then use your web-browser’s Web Developer Tools to inspect the HTML element structure it creates when that Passage is visited, you will see something like the following before either link is selected…

<tw-hook name="birds">
	<tw-expression type="macro" name="link-reveal" return="changer"></tw-expression>
	<tw-hook>
		<tw-link tabindex="0" data-raw="">Eagles</tw-link>
	</tw-hook>
	 or 
	<tw-expression type="macro" name="link-reveal" return="changer"></tw-expression>
	<tw-hook>
		<tw-link tabindex="0" data-raw="">Owls</tw-link>
	</tw-hook>
</tw-hook>

And after the Eagles link is selected that structure will change to…

<tw-hook name="birds">
	<tw-expression type="macro" name="link-reveal" return="changer"></tw-expression>
	<tw-hook>
		Eagles
		<tw-expression type="macro" name="set" return="instant"></tw-expression>
	</tw-hook>
		or 
	<tw-expression type="macro" name="link-reveal" return="changer"></tw-expression>
	<tw-hook>
		<tw-link tabindex="0" data-raw="">Owls</tw-link>
	</tw-hook>
</tw-hook>

And after comparing those two structures you will learn:

  • A Named Hook is represented by a <tw-hook> element with a name attribute that is assigned a lowercased variation of the Hook’s name.
  • An unselected Link is represented by a <tw-hook> element that contains a ‘child’ <tw-link> element.
  • The ‘child’ <tw-link> element is removed when a Link is selected.

1: Creating a custom JavaScript function in your project’s Story > JavaScript area.

window.setup = window.setup || {};

/* Assigns a known CSS class to the target Named Hook */
setup.disableLinks = function (hookName) {
	$('tw-hook[name="' + hookName + '"]').addClass('disabled-links');
};

The above first creates a “global like” Namespace named setup which is used to separate your custom JavaScript functions & variables from those of the web-browser and any other third-party library (like the Harlowe engine itself). It then adds a function named disableLinks() to that namespace that uses the jQuery addClass() function to assign a known CSS Class to the Named Hook whose Name is passed in.

WARNING: The above function has no error catching code added to it, and it doesn’t validate that the Hook Name passed to it is of the correct format or that it exists.

2: Create a custom CSS Rule in your project’s Story > Stylesheet area.

.disabled-links tw-link {
	pointer-events: none;
}

The CSS Selector of the rule targets any <tw-link> than is a ‘descendent’ of an element that has been assigned a disabled-links CSS Class. The pointer-events property of that <tw-link> element is used to disable its ability to be selected.

3: Alter the Passage content to call the new custom function.

{
(set: $eagle to 0, $owl to 0)

Which do you like more? |birds>[
	(link-reveal: 'Eagles')[
		(set: $eagle to it + 1)
		<script>setup.disableLinks('birds')</script>
	]
	or
	(link-reveal: 'Owls') [
		(set: $owl to it + 1)
		<script>setup.disableLinks('birds')</script>
	]
]
}

The above uses a HTML <script> element to execute the custom function when the related link is selected…

2 Likes

Thank you, Greyelf!

You went above and beyond to get me this solution. I really appreciate it.

1 Like