Help with a JavaScript function

Twine Version: 2.2.1
Story Format: SugarCube 2.27

I am starting out teaching myself Twine and to get used to using some JavaScript functions I am trying to create a really simple function that lets me check a list of flags to see if one exists.

The function in my Story Java Script is:

window.cPrompt = function (p, pList) {
var tList = State.getVar(pList);
var vResult=false;
for (let i = 0; i < tList.length; i++) {
	if(tList[i]===p)
		{
		vResult=true;
		}
	}
return vResult;
};

My Twine code to test it is:

<<if cPrompt('Test Prompt',$testPrompts)==true>>
Found the prompt!!!
<<endif>>

I set up the list of prompts in StoryInit:

<<set $testPrompts=['Test Prompt']>>

When I test it I get:
“Error: <>: bad conditional expression in <> clause: Cannot read property ‘length’ of undefined”

Could anyone point out where I am going wrong please?

Is $testPrompts a string variable or the name of the variable containing the flags. If it’s the name of the variable, you have to pass it as a string.

Therefore:

cPrompt("Test Prompt", "$testPrompts")

The issue is that State.getVar(pList) is probably returning undefined for the value of pList.


You can simplify the logic. Js arrays have a native find method.

window.cPrompt = function (p, pList) {
	return State.getVar(pList).find(prompt => p === prompt);
} 

You’re directly passing the variable’s value, the array itself, into the function, rather than the variable name, so using State.getVar() is both unnecessary and incorrect in this instance. That’s why State.getVar() is returning undefined.

Fixing that issue, your function should look something like:

window.cPrompt = function (p, list) {
	let result = false;
	for (let i = 0; i < list.length; i++) {
		if (list[i] === p) {
			result = true;
		}
	}
	return result;
};

That done, your function is searching the entire list, even if you find the needle (p) early—that’s not good. To fix that, it should stop as soon as finds the needle, which could look something like:

window.cPrompt = function (p, list) {
	let result = false;
	for (let i = 0; i < list.length; i++) {
		if (list[i] === p) {
			result = true;
			break;
		}
	}
	return result;
};

But, that can be done a bit more simply like so:

window.cPrompt = function (p, list) {
	for (let i = 0; i < list.length; i++) {
		if (list[i] === p) {
			return true;
		}
	}
	return false;
};

That said, you really should leverage the existing JavaScript APIs when you can. In this case there are several ways to accomplish your goal, the easiest being to use <Array>.includes():

window.cPrompt = function (p, list) {
	return list.includes(p);
};

Which you could simply use directly, like so:

<<if $testPrompts.includes('Test Prompt')>>
Found the prompt!!!
<</if>>

PS: The <<end…>> form of closing tags have been deprecated for years, I’d suggest getting used to the <</…>> forms (as in my last example).

1 Like

thornekin: You’re largely on point, but I do have a few comments.

You really don’t need to pass the variable name, for use with State.getVar(), if you already have direct access to the variable, and thus its value. Using it directly is sufficient.

As long as the OP simply wants to know if the needle exists within the haystack, <Array>.includes() is a better option than <Array>.find() since you don’t need to call, and create in your example, a predicate function.

Additionally, as written, your example will fail if used with falsy values since <Array>.find() returns the matching value itself, rather than whether it was found—that could be fixed by checking the returned value against the needle, but….

1 Like

You’re absolutely right. :grin:

Many thanks, that answered my question and much more :+1: