+1 vote
by (1.3k points)
edited by

So, I think I'll be having quite a long passage, the more I add to it. And it would be helpful if I could direct the user to a specific point in that passage when something new happens there.

I had thought to try something like:

[[link|AboutOthers#sistersFriends]]

But that's not right. Maybe it's not even possible to do this. What think? At the destination passage, I do have:

<a id="sistersFriends">@@.castTitle;~ your sister's friends ~@@</a>

So, the part I want to jump to is uniquely identified. Anyone know how I could do this kind of thing?

===

Actually, you know what? I don't like the fact that the titles are now clickable links. As demonstrated here:

https://ibb.co/d9m8um

So, how do I do this without it looking retarded?

===

So, I went ahead and changed it to this:

<span id="sistersFriends">@@.castTitle;~ your sister's friends ~@@</span>

So, now I just need to find out how to jump to it. :P Any ideas?

2 Answers

+1 vote
by (159k points)
edited by
 
Best answer

You could try using the Javascript Element.scrollIntoView() function (*) within a SugarCube postdisplay event handler.

Place the following Javascript example within the Story Javascript area of your story, it checks for the existence of a story variable named anchor, and if found it tries to locate an element with and ID of the variable value, and if found tries to scroll to that element. The anchor variable is deleted if it existed.

postdisplay["Scroll to anchor"] = function (taskName) {
	if (State.variables.hasOwnProperty('anchor')) {
		var el = document.getElementById(State.variables['anchor']);
		if (el !== null) {
			el.scrollIntoView();
		}
		delete State.variables['anchor'];
	}	
};

... you could then use a Setter Link (Link with Setter) like the following to activate the above handler.

[[Link Text|Target Passage Name][$anchor to "abc"]]

(*) web-browser compatibility table states function is not supported by Internet Explorer Phone.

by (159k points)
edited by

I don't exactly like reading technical docs

If you want to develop a game with non-trivial functionality then you better get used to reading documentation.

A couple of things:

1. As explained in the documentation you need to be using at least v2.20.0 of SugarCube 2 for the new events to work, so you will either need to be using the latest version of Twine 2 or have manually updated the version of SugarCube 2 yourself.

2. Putting the $(document).on(....) versions of the new event handlers within your Story Javascript area does work, I just tested it on a new project.

$(document).on(':passagedisplay', function (ev) {
	if (State.variables.hasOwnProperty('anchor')) {
		var el = document.getElementById(State.variables['anchor']);
		if (el !== null) {
			el.scrollIntoView();
		}
		delete State.variables['anchor'];
	}	
});

3. Putting the $(document).one(....) versions of the new event handlers within a <<script>> macro within the Passage(s) containing the ID'ed anchor elements does work, I tested this as well.

Some long block of text.

<a id="abc" />

Some other block of text.

<<script>>
$(document).one(':passagedisplay', function (ev) {
	if (State.variables.hasOwnProperty('anchor')) {
		var el = document.getElementById(State.variables['anchor']);
		if (el !== null) {
			el.scrollIntoView();
		}
		delete State.variables['anchor'];
	}	
});
<</script>>

4. The jQuery one() function documentation explains about it's parameters, and the Event object that is passed to the supplied callback function.

5. An element generally needs to exist within the current page's Document Object Model structure before you can scroll to it, this is why I suggested using a "post display" related task (or event)

edit:
6. The getElementById() function is a DOM function, not a jQuery one and as such should be generally replace with the jQuery find() function when trying to locate an element relative to an existing context. In the use-case you listed you could do something like the following (untested) if you wanted to use jQuery functionality to locale the ID'ed anchor element.

val el = $("#" + State.variables['anchor']);

 

by (1.3k points)

Well, thanks for your help. But, sometimes it makes sense to test things a little more carefully before dismissing them outright.

Steps for the experiment:

1. click link

(you'll see the result)

2. click back

3. click start2

4. click link

(you'll see the result)

What think? Did you notice anything? So, code:

:: start
[[link1|dest][$anchor = "abc"]]

[[start2]]

<<script>>
$(document).one(':passagedisplay', function (ev) {
	if (State.variables.hasOwnProperty('anchor')) {
		var el = document.getElementById(State.variables['anchor']);
		if (el !== null) {
			el.scrollIntoView();
		}
		delete State.variables['anchor'];
	}	
});
<</script>>\

:: start2
[[link1|dest][$anchor = "abc"]]

<<script>>
$(document).on(':passagedisplay', function (ev) {
	if (State.variables.hasOwnProperty('anchor')) {
		var el = document.getElementById(State.variables['anchor']);
		if (el !== null) {
			el.scrollIntoView();
		}
		delete State.variables['anchor'];
	}	
});
<</script>>\

:: dest
Some long block of text.
Some long block of text.
Some long block of text.
Some long block of text.
Some long block of text.
Some long block of text.
Some long block of text.
Some long block of text.
Some long block of text.
Some long block of text.
Some long block of text.
Some long block of text.
Some long block of text.
Some long block of text.
Some long block of text.
Some long block of text.

<a id="abc" />

Some long block of text.
Some long block of text.
Some long block of text.
Some long block of text.
Some long block of text.
Some long block of text.
Some long block of text.
Some long block of text.
Some long block of text.
Some long block of text.
Some long block of text.
Some long block of text.
Some long block of text.
Some long block of text.
Some long block of text.
Some long block of text.

I'm not sure you can actually copy that code from there. It seems to be an image or something. So, I might upload the html again. To make it easier to confirm.

Anyway, I'm not sure why it doesn't work. But it doesn't.

testtesttest.html

As for all the other info... thanks. Every bit helps. I don't intend to sit down and become a specialist in twine/sugarcube. I just want to write. A game. I just come here when something stops me from doing that. I like writing. That's what I do.

by (159k points)

There are a number of issues with your last example.

1. You are using both the singe-use version of the Event handler $(document).one( ... ) in your start Passage and the multiple-use version of the Event handler $(document).on( ... ) in your start2 Passage, and you should only be using one of them.
The multiple-use version would normally be setup within your story's Story Javascript area, and the single-use version would be setup each time you need it.

I strongly suggest you use the multiple-use version that is setup within your Story Javascript area..

2. You are incorrectly setting up your singe-use Event handler within the Passage containing the markup based link (your start passage), and I stated in point 3 of my previous comment that you need to setup that type of Event handler within the Passage containing the ID'ed HTML anchor element (your dest passage)

The need to setup single-use Event handlers within each & every possible Target Passage you want the scroll-to behaviour in is one of the reasons why I originally suggested using the Task version that is setup with your Story Javascript area, and why I then suggested using the Event version that is setup in the same area.

by (1.3k points)
I see! I didn't have a firm grasp of the overall layout. The mechanism. But, now that it's explained, I understand.

As for what version of it I use... I tend to favor the one that doesn't get automatically called on each and every passage transition. But, since I really have no idea what the overall efficiency of the sugarcube header is, I can't say that it's even a worthwhile pursuit. It probably isn't.

But, right now, the only destination is one passage. The idea being that it'll exponentially expand as the story progresses. Hence the idea for a direct link to the sections as they update.

I'm likely going to be moving away from that super massive passage, as I go on though, so it's maybe a moot point.

I think this answers everything. So, I'm happy to just leave it here. It works. Works great. Thanks.
by (1.2k points)
Thank you for putting me onto scrollIntoView()! It allowed me to solve an issue I'd been putting off fixing with just a few lines of code! Cheers!

(The issue in question being how to best append text messages to a virtual phone interface without the player needing to scroll down to see the latest message as it was appended.)
0 votes
by (63.1k points)
edited by
<a id="sistersFriends">@@.castTitle;~ your sister's friends ~@@</a>

 You kind of did this backwards.  I think you wanted something like:

<a href="#sistersFriends">Link</a>

@@.castTitle;#sistersFriends;~ your sister's friends ~@@

The above creates a link that will bring the #sistersFriends element into view.

So, I went ahead and changed it to this:

<span id="sistersFriends">@@.castTitle;~ your sister's friends ~@@</span>

So, now I just need to find out how to jump to it. :P Any ideas?

Using the span will also work, but this is completely redundant:

THIS CODE:
<span id="sistersFriends">@@.castTitle;~ your sister's friends ~@@</span>

IS PARSED INTO:
<span id="sistersFriends"><span class='castTitle'>~ your sister's friends ~</span></span>

THIS CODE:
@@.castTitle;#sistersFriends;~ your sister's friends ~@@

IS PARSED INTO:
<span id="sistersFriends" class='castTitle'>~ your sister's friends ~</span>

You can use either the custom style markup (i.e. @@ ... @@) or the equivalent HTML, but generally you'll want stick to one or the other. And you will want to avoid making more elements than you need.  Your code is generating two elements instead of one, and while it may not seem like a huge deal, it is wasteful.

Anyway, this solution is a bit rough; it'll show up as an external link and it will snap the window to the correct place instantly.  Here's a slightly more professional-looking approach:

In story JavaScript:

setup.scrollToID = function (id, time) {
    if (!id || typeof id !== 'string' || id[0] !== '#') {
        console.error('scrollToID() -> ' + id + ' is not a valid selector.');
        return;
    }
    
    time = Number(time);
    if (Number.isNaN(time) || time < 0) {
        time = 500;
    }
    
    $('html, body').animate({
        scrollTop: $(id).offset().top
    }, time);
    
};

Then, in a widget-tagged passage:

<<widget 'linkToSel'>>\
    <<link $args[0]>>
        <<run setup.scrollToID($args[1], $args[2])>>
    <</link>>\
<</widget>>

And finally, to use it:

<<linkToSel 'link text' '#selector' time>>

    * 'link text' - the text of the link
    * '#selector' - a selector, must be an ID
    * time - optional, must be in milliseconds, determines length of animation, default 500ms

/% using your example %/

<<linkToSel 'Click here.' '#sistersFriends'>>

[...]

@@.castTitle;#sistersFriends;~ your sister's friends ~@@

Clicking a link generated by this widget should scroll the part of the page you want into view smoothly.

Edit. 

See below. 

by (68.6k points)

I think you wanted something like:

<a href="#sistersFriends">Link</a>

@@.castTitle#sistersFriends;~ your sister's friends ~@@

The above creates a link that will bring the #sistersFriends element into view.

That use of the custom styles markup is invalid.  You cannot, currently, conjoin IDs and classes, so it needs to be something like the following (with a semi-colon in between):

@@.castTitle;#sistersFriends;~ your sister's friends ~@@

/* OR */

@@#sistersFriends;.castTitle;~ your sister's friends ~@@

 

by (1.3k points)

Hmm, thanks for all the help, Chapel.

But, I'm encountering some problems. I started off putting it all in my on-going project. But, encountered the error.

So, I made a very simple project with only this code in it. And I encounter the same problem.

If fact, neither of these methods seems to work. 

https://ibb.co/fbCUH6

If I publish it, and use an external browser, the first method does seem to want to go to: TestingTesting.html#sistersFriends

But, it doesn't seem to move to the passage. It just stays on the starting page.

Oh, and the picture that shows the error message is from using the second method.

If you wanna look at the code, here's a compiled version: testtesting.html

I could supply the project code too, but I'm not sure how you'd import that or stuff. But, really it is only the code that you already shared here.

So... any good at debugging? :P

by (63.1k points)

But, it doesn't seem to move to the passage. It just stays on the starting page.

Currently, both methods I provided require the links and the content being linked to to be on the same passage, which seemed to be what you were asking for. While a link that navigates passages and then adjusts the scroll is possible, it's a bit strange. 

At any rate, if it's for some reason not possible to have the links in the same passage as the content, I can try to work something out for you, but not until I get back to my computer later on. Someone else might be able to help in the mean time. 

by (1.3k points)
Yeah, thanks for the offer.

I did think of how I could alternatively structure the content. I'm only really starting out writing my game, so a lot of what I'm doing now could change in the future. And things that I haven't even thought about might be required down the track.

So, right now, I'm thinking to have a link (from the sidebar) that takes the user to a list of cast. The cast page will be updated when characters are introduced. Filled that way. From there, I'll have links to the sections I'd thought to use the current question stuff with. It probably won't be needed though, since each write-up will be on its own passage.

In the long term, I think this would be a better approach for me to take in any case. Because the sections themselves will work a bit like a diary. Something in the story happens, and thoughts about it are later revealed  in this "diary section". I had thought to copy over the entries, to keep things from blowing out, but now I see that I could keep everything, so the user can flip back and read the entries in sequence.

I haven't thought of how to do that, but I've seen webpages that do similar. A navigation bar up top that shows links to pages. And then the page with the content underneath that. So... I think I could probably just go with that.

Then, when I have a link in the story that takes them to that updated entry, it'll just be a simple passage transition, like [(link)|sistersFriends03]. I'm not sure how my proposed navigation bar works with that. But, I'll have a play and maybe ask another question if I can't work it out.

But, thanks a lot Chapel. I really appreciate your solution to this. I'll refer back to it if I should have a situation with link and content on the same page. You obviously are pretty talented with this stuff. :P
by (100 points)
I have been looking for a solution to this as well, specifically to create a glossary for the player. Ideally, the player runs into a word he doesn't recognize and clicks the glossary, and it links directly to the relevant entry in the Glossary passage.
by (63.1k points)
The code @greyelf posted should work for that.
...