Outils pour utilisateurs

Outils du site


issue146:inkscape

Ceci est une ancienne révision du document !


If you followed last month’s tutorial, you should now have a styled button that toggles between two classes when you click on it. This month, we’re going to extend the code behind that button to control other objects on the screen, so if you didn’t play along with the previous article, now is the time to go back and give it a try.

We’ve previously looked at two ways to put JavaScript into an Inkscape file: directly in the fields within the “Interactivity” section of the Object Properties dialog; and via the “Embedded scripts” tab in the “Scripting” section of the Document Properties dialog. You may recall that the latter location also includes a second tab, labelled “External scripts”. This month we’ll take a look at that tab, and discuss the pros and cons of this approach.

First, load the SVG file for the button that you created last time into Inkscape. Then open the File > Document Properties dialog, and select the “Scripting” tab. Within that section make sure the “External scripts” tab is active.

As with the “Embedded scripts” tab, the UI here is sparse and slightly misleading. At the top is an excessively short area that lists any external script files linked to from your document – it should be empty at the moment. Below that is an unlabelled line, with Plus (+) and Minus (-) buttons after it, the latter being disabled at this point. As the name of the tab suggests, this UI is used to link external scripts – i.e. JavaScript code stored in a separate file – to your SVG file. You have probably also guessed that the Plus button adds a file to the list above, whilst the minus button removes it. What isn’t so clear is that the Plus button actually works in three different ways, depending on the content of the unlabelled field, and whether or not your JS file exists yet.

Method 1: If you just click on the Plus button, you’ll be presented with a file selector dialog. You can then navigate to the directory of your choice and enter a new filename into the file selector. When you accept the content of the file selector (e.g. by clicking the “Open” button), the full path and name of your file will appear in the list at the top of the dialog. NOTE: This does not actually create the file on-disk, so you’ll need to do that manually, opening up the possibility of typos.

Method 2: This is the same as the previous method, except this time you use the file selector to choose a file that already exists. Once again, the full path to the file is put into the list at the top. This has the advantage over the previous method that you can’t make a typo with the filename.

Method 3: Type something into the unlabelled box, then click the Plus button. Whatever you typed will be added to the list of files at the top of the dialog. Of course, it only really makes sense to type the name (and possibly path) of a JavaScript file, but this field does no error checking so typos and bad paths are all too easy to introduce.

You might think that the third method is to be avoided – the possibility of making a typo is too high. But, in practice, this is the only method of the three that I recommend using! In just about every case, you will want to keep the JavaScript file close to your SVG file: if not in the same directory, then usually just one level away in a “scripts” or “resources” folder. The third method lets you supply just the filename, or a relative path and filename (eg. ‘scripts/button.js’), which will still be valid if you move your SVG and JS files to another machine, or a different location on your hard drive.

The first two methods, on the other hand, produce absolute paths which will break as soon as the files are moved. And you can’t edit the paths within this dialog – you have to modify them via the XML editor. So not only is there a chance of introducing typos as you edit them, but you have to be comfortable with using the XML editor to do so. Better, I think, to use method 3 and just be careful with what you type.

To keep things simple, let’s create a new JavaScript file in the same directory as the main SVG file. Using a text editor, create a new file containing the following line:

alert(“Loaded”);

Save the file to the same directory as your SVG image, with a “.js” extension. In my case the Inkscape file is called “button.svg”, so I’ll give the JavaScript file the name of “button.js” for clarity, though it’s not a requirement for them to share the same name. Back in the Document Properties dialog, I can now type the filename (with no path) into the unlabelled text field, then click the Plus button to add it to the list at the top, so the dialog looks like this:

If everything has been done correctly, loading the SVG file into a browser should result in an alert being shown containing the word “Loaded”. That, at least, tells us that the link from SVG to JS files is working. It’s better to do a lightweight test like this first, before fleshing out your JavaScript code, to make sure the basics are in place.

We want our button to control another object when it’s clicked – which will be a lot easier if we actually have another object to control! In Inkscape, alter the file to add a simple filled circle, making sure to give it a sensible ID via the Object Properties dialog (I called mine “redCircle”).

Right-click on the button, and bring up the Object Properties dialog. Within the “Interactivity” section at the bottom, remove any existing code then, in the “onclick” field, add the following JavaScript function call:

buttonPressed();

This function doesn’t actually exist yet: we need to add it to our JS file. Using a text editor, remove the existing alert() and replace it with this:

function buttonPressed() {

alert("Button pressed");

}

Save both files, then reload the SVG file in a web browser, confirming that both elements are visible, and that a click on the button shows the new alert() dialog.

Now that we’ve got a function that runs when the button is clicked, we want to populate it with some code to change the fill color of the circle. Previously, we’ve changed the fill color of the object being clicked, either by explicitly setting “this.style.fill”, or by modifying the classes of the clicked object using “this.classList.toggle()” and similar functions. Altering the style of a different object is essentially the same, except that we no longer use “this” to identify the target for our changes. Instead we need to get a reference to the target object in a different way.

Depending on exactly what you are trying to do, there are various approaches that could be used. But the simplest, at least conceptually, are a pair of methods on the “document” object (which exists implicitly on all XML and HTML documents): querySelector() and querySelectorAll(). The difference between them is that the former returns a single XML node, whereas the latter returns a collection of nodes. A collection is similar in some respects to an array in JS, but doesn’t have all the standard array methods, so needs to be treated a little differently. For this example, however, we want to change the style of only a single element, so document.querySelector() will do the job.

Both methods take a single parameter: a string containing a CSS selector. If the selector matches more than one element then querySelector() just returns the first one. If it matches no elements, the method returns “null”. In JavaScript terms, “null” is what’s referred to as a “falsey” value – that is, one that evaluates to “false” when you use it in an “if” statement. That makes it pretty simple to write defensive code that won’t fall over if your CSS selector doesn’t match anything. Let’s take a look at an example, by replacing the alert() in your buttonPressed() function (top right).

Save the JS file, then reload the SVG in the browser. With the developer tools open (press F12, if necessary), and the console tab selected, click on the button in your file. You should see the <circle> element logged out – or the string “No circle found!” otherwise. If you saw the latter, it suggests that the CSS query doesn’t suit the structure of your file. Here are a few things you can look at to help troubleshoot this: • Did you remember to put the # in front of the ID in the CSS? • Does the ID match the one you put on the circle in Inkscape? • Try changing the selector to match an element (eg. “circle”) instead of an ID. • Open your SVG file in a text editor and search for your ID. Is it definitely present in an ID attribute on the element you expected?

Now that we’ve got a reference to the circle, it’s a pretty simple task to set the style explicitly, or modify its class list. Here’s the buttonPressed() function (below) rewritten to directly set the fill color on the circle. Note that I’ve removed the “else”, as we just want the code to fail silently with no side-effects if the CSS selector fails to match anything.

Despite what I said earlier, the querySelector() method doesn’t actually return the SVG node, as such, but rather a JavaScript object that references the element in the browser’s internal document structure. Usually you can ignore this subtle distinction, but it does mean that we have a JS object in hand, which offers us a few advantages when writing our code. The object has a variety of properties and methods attached to it but we can also attach our own. This can be a useful way to keep track of data that needs to persist outside our function.

Consider trying to toggle the color in response to the button presses. You could use a CSS class, and call the circle.classList.toggle() method to alternately add and remove it. This is similar to the approach we took last time, except we’re referencing our object variable rather than “this”. But what if you want to set the fill color on the circle directly, rather than via a class? You could read the value of circle.style.fill back, and test to see what it’s currently set to. But there are various ways to define colors in CSS, so you might not get back the format you expect.

A better approach is to create a property on the object that you can refer to each time the function is called. You can test its current value, then set it to something else before your function finishes. You don’t need to do anything fancy to create a property like this – the browser will create it as soon as you try to use it – so the code ends up looking like that shown next page, bottom left.

The first time you click the button the “isOn” property doesn’t exist. That means the test in the “if” statement fails, and the code in the “else” runs – setting the color to green, and creating the “isOn” property, with a value of “true”. The next time you click the button the “if” succeeds, the fill is set to red and the property is set to “false”. Thereafter the color and property will continue to toggle each time you click the button.

One important thing to note as we’ve been editing this code is that you haven’t had to go anywhere near Inkscape since the initial setup. This is one big advantage of using external, linked scripts, as there’s less chance of accidentally making changes to your SVG file. You also get all the benefits of using a proper text editor: with any half-decent editor you should get syntax highlighting and other aids, which can help to indicate problems in your code. Plus, being able to work in a proper sized window, rather than the single line of the Object Properties dialog or the letterbox of Inkscape’s Embedded Scripts tab, is a huge advantage.

When deploying an SVG file with a linked script, you need to make sure that the script is still accessible to the SVG file once it’s on to your web server – typically by ensuring you use a relative path as outlined at the top of this article. You have to make sure you remember to keep your JS file in sync with any changes to your SVG file or its location. But this additional housekeeping is usually more than worth it. Generally, unless you really are writing only a single line of code, or perhaps a single, short function, linking is the way to go.

issue146/inkscape.1562059497.txt.gz · Dernière modification : 2019/07/02 11:24 de auntiee