Progress bar Twine Sugarcube 2.0 "Cannot read property 'querySelector' of null"

Hi I’m quite new to Twine Sugarcube and I have hit a hurdle I get this error Cannot read property ‘querySelector’ of null, and I can’t see what is wrong with it.

Twine
HTML:

<div class="progress-bar"><div class="progress-bar-fill"></div></div>      

CSS:

progress-bar {
    width: 200px;
    height: 10px;
    border : 1px solid #000;
    border-radius: 25px;
    position: relative;
}
.progress-bar-fill {
    left: 0;
    top: 0;
    height: 100%;
    background:  #009900 ;
    transition: width 0.5s;
    border-radius: 25px;
}

JavaScript:

class ProgressBar {
    constructor (element, initialValue = 0){
        this.fillElem = element.querySelector('.progress-bar-fill');
        this.setValue(initialValue);
	}

	setValue (newValue){

		if (newValue < 0){
                    newValue = 0;
	}

		if (newValue > 100){
                    newValue = 100;
	}
	this.value = newValue;
	this.update();
        }		
        update (){

	const percentage = this.value + '%';
	this.fillElem.style.width = percentage;
        }
}
const pb1 = new ProgressBar(document.querySelector('.progress-bar'), 75);

This all works in a HTML document so what do I need to do different in Twine?

There are a number of issues with your code examples:

1: Any JavaScript code found within a Twine 2.x project’s Story JavaScript area or within any script tagged passage is executed before the contents of the first Passage of a story is processed. This means that at the time the document.querySelector('.progress-bar') function call is made the <div class="progress-bar"> element won’t exist yet, which is why you’re seeing that error message.

You would need to delay the calling of the document.querySelector() function until after the target div has been added to either: the buffer used by the Passage Processing process; or to the page’s DOM. The need to delay the calling of the function leads to the 2nd issue.

2: Each JavaScript block, like that found within: the Story JavaScript area; a script tagged passage; a external JS file; are executed within their own private scope. This means that variable/classes/modules defined within one block are not visible to another block, unless you use some means to evaluate the scope of that thing to a ‘global’ like state.

There are a number of methods you can use to make something ‘global’ like:
a: define it on the special SugarCube setup object

setup.doit = function () {
	console.log('doit called');
};

b: define it on your own personal Namespace.

window.GE = window.GE || {};

GE.doit = function () {
	console.log('doit called');
};

c: define it on the web-browser’s special window object

window.doit = function () {
	console.log('doit called');
};

note: each of the above methods have their pros & cons.

1 Like