What's the easiest (or best, not necessarily the same) way to generate unique ```<div>``` ids?

Tweego Version: v2.1.1+81d1d71
Story Format: SugarCube v2.31.1

What’s the easiest or best way to generate unique <div> ids?

Background:

I’m currently “hiding” optional text by using this construct:

<<link "Description.">><<toggleclass "#hidden" "hide">><</link>>
<div id="hidden" class="hide"> <<include  "one of many descriptive passages">></div>

“hide” is defined in “Story Stylesheet” as

.hide { display: none; }

Unfortunately, each “hidden” <div> id has to be unique since an arbitrary (and random) number of these hiding directives can appear on a page. I’ll have more than 200 “hide” directives and I’d rather not manually type in that many unique div ids if I can avoid it.

What’s the best way to do this?

When I did a Web search for “unique Javascript names” (or similar) I found that most of them recommended installing any of a number of 3rd party javascript libraries. Before I head down one of those rabbit holes, I hoped that someone might have solved this type of problem already.

FWIW, my immediate thought was to use some kind of text preprocessor like, perhaps, m4 – a rabbit hole of yet another kind.

Thanks in advance for whatever suggestions you might have.

I think you may be over thinking it. What I usually do in this situation is I create a variable (I’d use a temporary one if it worked, or a story one if it doesn’t) and just increment it by one before creating each ID.

Macro.add('hidden', {
    handler: function() {
        if (!setup.hidden_count) setup.hidden_count = 0;
        var hidden_id = "hidden_" + setup.hidden_count;
        setup.hidden_count += 1;
        
        var output = '<div id="' + hidden_id + '" class="hide"><<include "' + this.args[1] + '">></div>' +
                     '<<link "' + this.args[0] + '">><<toggleclass "#' + hidden_id + '" "hide">><</link>>';
                     
        $(this.output).wiki(output);
    }
});

You just use the macro as <<hidden "link text" "passage name">>.

@tayruh

Thanks, a lot!

It wasn’t so much “overthinking” as not knowing how to write a macro at all, so I hadn’t even considered a solution like yours.

After some trivial mods to make it better match my own proclivities, your example works great!

(Specifically, in addition to putting the included text after the “unhide” command, I gave it an optional 3rd argument so that a “hide description” can be provided after the block of text, just in case the included text is so long that the initial “unhide” is awkwardly distant.)

Thanks, again!

1 Like

I prefer macros over widgets. For one, I think they run a bit faster. But more importantly to me is that they avoid any of the weird spacing issues that widgets have (no need to escape lines or use <<nobr>>) and they write their variables directly into the output strings. The last reason means that you don’t need to use <<capture>> for the solution that I just gave you, which you would have had to do in a widget. The only downside is that variables like $blah don’t work, but you can still access them with variables().blah, which isn’t too bad.

Or by using the State.variables collection object, which doesn’t involve a function call (by you) to access that collection. eg. State.variables.blah

Yeah, but that’s a longer name and I’m lazy. Unless it’s in a loop, the extra 0.01ms it takes to do a function call isn’t going to kill anyone. I also sometimes use a getter to make the variable name shorter, but that’s still overhead. shrug

There’s absolutely nothing special about JavaScript macros versus widgets in that regard. Your example macro didn’t need to capture the value because it uses the equivalent of the Stupid Print Trick™ to prerender it. You can do, used to have to do, the same thing in widgets. The <<capture>> macro was added specifically so that you don’t have to do that—in widgets or loose code.

Hmm. I was trying to get it to work without <<capture>> in the widget and it was still printing the same ID for each. But it was like 3am and I was probably just doing something dumb somewhere. That’s why I settled on the sure-fire way using the macro. :sweat_smile:

Here’s a much simpler method. First, put this in your Stylesheet section:

.hide {
	visibility: hidden;
}
.noselect {
	-webkit-touch-callout: none;  /* iOS Safari */
	  -webkit-user-select: none;  /* Safari */
	   -khtml-user-select: none;  /* Konqueror HTML */
		 -moz-user-select: none;  /* Firefox */
		  -ms-user-select: none;  /* Internet Explorer/Edge */
			  user-select: none;  /* Non-prefixed version, currently
									 supported by Chrome and Opera */
}

and then in your passage you can just do this:

<div><a class="noselect" onclick="$(this).siblings('div').first().toggleClass('hide')">Description</a>
<div class="hide"> <<include "one of many descriptive passages">></div></div>\
Other text.

Instead of looking for an ID, that code uses jQuery to find the next <div> after the link, and toggle the “hide” class on that.

Also, instead of “display: none;”, that uses “visibility: hidden;” for the “hide” class, so that the text below that doesn’t move when you click on the link. The “noselect” class is there to keep the link from getting selected if the user double-clicks the link.

If you wanted to make that a bit easier to use, then you could turn it into a macro by adding this to your JavaScript section:

/* Hidden Text - Start */
Macro.add("hid", {
	skipArgs : true,
	tags     : null,
	handler  : function () {
		var txt = '<div><a class="noselect" onclick="$(this).siblings(\'div\').first().toggleClass(\'hide\')">';
		if (this.payload[0].args.full.length > 0) {
			txt += this.payload[0].args.full;
		} else {
			txt += "Description";
		}
		txt += '</a><div class="hide"> ' + this.payload[0].contents + '</div></div>';
		$(this.output).wiki(txt);
	}
});
/* Hidden Text - End */

and then in your passage you’d do something like this instead:

<<hid Description>> <<include "one of many descriptive passages">><</hid>>\
Other text.

Please let me know if you have any questions on any of that.

Hope that helps! :slight_smile:

Thanks for the alternative!

It does indeed work as you describe, including the macro.

A limitation with it, though, is that it displays only a single subsequent <div>. If the included passage contains one or more <div> sections of its own, perhaps for text formatting, vertical space is allocated for displaying them but they aren’t actually revealed.

I don’t see any easy way around that, but I’m very much a novice at JavaScript programming.

That should work just fine. Just don’t set the “hide” class in those <div>s, since they’ll already be hidden.

Yup. You’re right!

I had a bug in the <div> that I was using.
I hate bugs :wink: