Importing jscolor to Twine?

Twine Version: 2.3.13

I’ve been learning how to add images to a story, and wanted to have some freedom over changing the colors of those images on the fly. The jscolor library looked like a great plug-and-play option, but I’m unable to get it to work in my story.

Even just wrapping verbatim html tags around their “simple hello world” example doesn’t work as expected - the button may appear, but clicking it fails to open the color-grid window. Haven’t gotten beyond that point to test whether the selected color can actually edit, say, a background color for a div. (My plan was to use css’s mix-blend-mode to set the color of a div, then blend it with grayscale drawings)

Sample twinescript:

<html> 
<script src="[jscolor.js](https://jscolor.com/release/latest.zip)"></script> Your favorite color: <input value="ab2567" data-jscolor=""> 
</html>

In my javascript story page, I’ve imported jscolor properly to my knowledge with no errors. Is this library just not functional with Sugarcube, or am I doing something wrong?

It looks like your link is pointed at a downloadable zip file which will not work unless it is unzipped and you are hosting it yourself.

If you want to use a version that is already hosted, I found this link on the js.color website;

https://cdnjs.cloudflare.com/ajax/libs/jscolor/2.5.1/jscolor.min.js

Too true! However, when I try to make it point to the correct directory where I’ve installed it, I’m getting the same results. The button shows up fine, the pop-up color grid doesn’t make an appearance.

Which compiler are you using? Twine 2 I assume.

How, exactly, are you attempting to import the library?

I have tried these setups to import.

In Edit Story Javascript:

importScripts(
	"https://cdnjs.cloudflare.com/ajax/libs/jscolor/2.5.1/jscolor.min.js"
	);
importScripts(
	"File:\\\F:\...\jscolor.js" //where ... is my actual file path to the unzipped js file
	);

In the launch passage:

<html>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jscolor/2.5.1/jscolor.min.js"></script>

Your favorite color: <input value="ab2567" data-jscolor=""></html>

Happy to be told how I’m messing this up! And yes, it’s Twine 2.

First. One of your issues likely stems from the fact that many libraries are not intended to work with SPAs (single page applications) in their simplest use case, which is usually the example they give. The issue here is likely that jscolor is not deteccting content changes, so you’re going to have to tell it to look for elements bearing a data-jscolor attribute when those happen—SugarCube has an event to help with that. NOTE: After reading its documentation, I think I’ve identified what method you need to call.

Anyway. With Twine 2 you have two relatively easy ways to do this. I recommend the first option, A.

Option A: Bundle jscolor into your project

1) In the ZIP archive you downloaded, there’s a file named jscolor.min.js. Open this file in a plain text editor—e.g., Notepad; do not use a word processor like Word, Wordpad, etc.

2) Select all, the entire contents of the file, and copy.

3) Paste the contents into your project’s JavaScript area.

4) Paste the following code into your project’s JavaScript area after the jscolor chunk.

// On each `:passageend` event attempt to attach jscolor
// instances to elements bearing a `data-jscolor` attribute.
$(document).on(':passageend', function () {
	jscolor.install();
});

Done. Use jscolor as normal.

Option B: Include jscolor from an external source

1) Paste the following code into your project’s JavaScript area.

// Lock the loading screen.
var lockId = LoadScreen.lock();

// Import jscolor from a CDN.
importScripts('https://cdnjs.cloudflare.com/ajax/libs/jscolor/2.5.1/jscolor.min.js')
	.then(function () {
		// On each `:passageend` event attempt to attach jscolor
		// instances to elements bearing a `data-jscolor` attribute.
		$(document).on(':passageend', function () {
			jscolor.install();
		});

		// If the starting passage has already been shown, show it
		// again so that jcolor can run on it.
		if (State.turns > 0) {
			Engine.show();
		}

		// Finally, unlock the load screen.
		LoadScreen.unlock(lockId);
	})
	.catch(function (err) {
		// There was an error loading the script, log it to the console
		// and unlock the load screen.
		console.error(err);
		LoadScreen.unlock(lockId);
	});

Done. Use jscolor as normal.

2 Likes

Thank you! This is the solution I needed, the box is now working in the passage. Now, on to figure out the image/background blending on grayscale.

Fun fact: Reloading my image (swapping to a different armor) on the UI-bar passage results in the script failing to load due to a bad CORS request (file instead of HTTP). Do I need to host my story on a local server and access it from there just to avoid this, or is there a better way to test it out in firefox?

Additionally, my in-line function in the passage directly after the jscolor input function is failing - I’m getting “Uncaught ReferenceError: update is not defined” when I thought I’d defined it right there.

<html>
<script src="jscolor.js"></script>
<script>
function update(picker) {
    document.getElementById('primarycolor').style.background = picker.toBackground();
    document.getElementById('primarysecondarycolor').style.background = picker.toBackground();
}

jscolor.trigger('input'); // triggers 'onInput' on all color pickers when they are ready
</script>


<script src="jscolor.js"></script>

<input value="ab2567" data-jscolor="{value:'ab2567'}", onInput='update(this.jcolor)'>


</html>

Why are there <html> and <script> tags in that example? Especially ones, in the latter case, attempting to load jscolor again, multiple times no less.

Story formats are single page applications, not simple web pages. You should not throw any old HTML into a passage and expect it to work like it was just a web page.

If you’ve successfully used the examples I gave earlier, not only shouldn’t you need most of what you have now, but using it could break what you’re attempting to accomplish here.

Anyway. Move the function into your project’s JavaScript area and rewrite it like so:

window.updatePicker = function (picker) {
	document.getElementById('primarycolor').style.background = picker.toBackground();
	document.getElementById('primarysecondarycolor').style.background = picker.toBackground();
};

Doing so will ensure that the function is available for use globally.

Then try just the following in your passage:

<input value="ab2567" data-jscolor="{value:'ab2567'}" oninput="updatePicker(this.jscolor)">
<<done>>
	<<script>>
	jscolor.trigger('input');
	<</script>>
<</done>>

NOTE: I also corrected a misspelling in the oninput attribute. Originally it was this.jcolor, rather than this.jscolor.

Thank you for being patient with me, as I’m sure you’re aware, the answer to most of your “why” question boils down to “because I don’t really know what I’m doing.” The extent of my scripting knowledge lies in simple C# and I’m very unfamiliar with javascript, css, or html.

I had to go and update sugarcube so that I’d have a <<done>> macro, and now that I’ve finished that, there are no errors to display, but the copy/pasted script from your answer just displays in the passage as below, verbatim. It seems like it’s not triggering it to process the script.

<input value="ab2567" data-jscolor="{value:'ab2567'}", onInput="updatePickerPrimary(this.jscolor)">

If by script you mean the <input> tag, try removing the erroneous comma after the data-jscolor value. E.g.,

<input value="ab2567" data-jscolor="{value:'ab2567'}" oninput="updatePickerPrimary(this.jscolor)">