jQuery/AJAX Network Error

It’s me again. (Asking a lot, so I’m hoping to give back a lot, too)

So, since my story javascript was getting massive while using the setup object to create all the various items and such I need for my game, and I have barely even gotten started, I’ve decided it would be best to either (a) Do it all in an external script and use importScripts() to bring it in, or (b) use JSON to store the data. I’m partial to the second option because that is the purpose of JSON and I’ve had success with it in previous projects.

In previous projects, I used plain AJAX scripts (the XMLHttpRequest object directly) to import the JSON and it was in the previous version of Twine when testing your game would open it in Twine’s tool instead of the browser. Now, I’m using jQuery AJAX and the project is opened in the browser: it’s not working, and I’m not sure which of these is causing me grief.

Here is how I’m sending the request:

if (window.hasOwnProperty("storyFormat")) {
	setup.path = "C:/Users/phoen/Documents/Twine/Stories/TheFeydrinMeadows/";
} 
else {
	setup.path = "";
}
var dataSources = [
	"src/test.json"
];

// Import data
for (var i = 0; i < dataSources.length; i++) {
	var options = {
		url: setup.path + dataSources[i],
		type : "get",
		async : false,
		cache : false,
		error : function (x, status, error) {
			alert("Error loading " + i + ": " + error);
		},
		success : function (res, status, xhr) {
			var obj = JSON.parse(res);
			for (var prop in obj) {
				setup[prop] = obj[prop];
			}
		}
		
	};
	$.ajax(options);
}

And here is my test JSON file:

{
"Range" : [
	"melee",
	"reach",
	"short-range",
	"mid-range",
	"long-range"
]
}

I’m certain the file path is correct, but my error function is running and its out put is:

“Error loading 0: Network Error: a network error occurred”

I’ve been fiddling with this for a while but can’t seem to find the issue. My knowledge of how all this works is limited, so I thought I would ask here in case I’m making an obvious mistake.

NOTE: I’m using “async = false” because I need my story script to hang until this data is loaded. I have code that builds objects from various pieces stored in the database, and if those loops run before the data is loaded, the objects won’t be built.

You can see more information about the access error that is occurring by opening the Console of your web-browser’s Web Developer Tools, the following explanation is in relation to Chrome but should be similar to other “modern” web-browsers.

Because you are trying to open a “local” file Chrome will of also issued the following error in the console. (this error is what is causing the one you are seeing in the error dialog)

Access to XMLHttpRequest at ‘file:///<…file-path info…>’ from origin ‘null’ has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.

Simply put, you can’t use AJAX (XMLHttpRequest) to access “local” external files (via the “file://” protocol) in web-browser’s like Chrome.

As to why the code previously worked within the older NWJS (Node-Webkit) framework based releases of the Twine 2.x application, I would “guess” that would have something to do with either: the way the older application instantiating the framework’s built in custom HTML/web-page viewer during Test & Play modes; or maybe the version / configuration of Chromium being used by the NWJS framework version that was used to build the application.

1 Like

Well, that’s unfortunate. Is there no way to import local JSON files using Twine?

This isn’t a Twine issue as such, the same outcome would occur for any locally viewed HTML file that tried to use AJAX to access a file using the file:// protocol.

One kludge I have seen used in a Twine project that supports the loading of external image packs, is to transform the JSON file into an “equivalent” JS file, their then used the importScripts() function to load this file instead.

So the transformation of your “Range” based example into a JS file may look something like the following.

window.GE = {
	"Range" : [
		"melee",
		"reach",
		"short-range",
		"mid-range",
		"long-range"
	]
}

note: I am using a GE (Greyelf) namespace in the above example to isolate the loaded content from the properties that already existing on web-browser’s window object, I suggest you use your own uniquely named namespace instead.

1 Like

Alright, so I should have gone with option (a) after all.

Thanks for the help, but I do have one question related to this particular method:

I understand that the returned Promise object can be used to detect whether the external scripts have been loaded. However, the rest of the Story JavaScript will still be running while this is happening. Is there an elegant way to make the script “hang” until it’s finished, or do I need to put all of my data-handling code inside the .then{} block?

Well, there is a third option: simply keep the “loading” screen up until everything has loaded.

I use something similar to the following code myself:

var lockID = LoadScreen.lock();  // Lock loading screen
importScripts("jquery-ui.js")
	.then(function() {
		Engine.play(passage(), true);  // Reloads current passage to trigger :passagerender event so that any jQuery UI code there can function
		LoadScreen.unlock(lockID);  // Unlock loading screen
		lockID = 0;  // Other code can check if lockID == 0 to see whether jQuery UI has loaded
	}).catch(function(error) {
		alert("Error: Could not find file 'jquery-ui.js'.");
	}
);

See the SugarCube LoadScreen API for details.

Hope that helps! :slight_smile:

1 Like

I was wondering if the LoadScreen API would come in handy eventually; it’s just that the documentation doesn’t really clarify how it works so I was unsure.

Thank you both very much. (I can only mark one of your posts as having solved the problem, but I appreciate all the information).

You might want to look at Engine.show() for the idiomatic form of what you’re doing there.