Sg2.8
User Input and Feedback
Index RSS

Scriptographer offers many ways for scripts to interact with the user. The UI package contains a complex library to create User Interfaces in Adobe Illustrator, maybe too complex for most simple scripts and novice users. But luckily Scriptographer also defines ways to very easily output values, bring up alerts, request values from the user and even create floating palette windows. This tutorial will explain the most commonly used mechanisms.

Please note:

Many of the examples below require features only present in the latest release of Scriptographer version 2.7.037 which was released on 08. March 2010.

Printing to the Console

Scriptographer's console is the place where all 'low-level' feedback goes. If an exception is thrown anywhere in a script and not handled, it is printed to the console. If a script fails to compile, the error is reported in the console. But scripts can also write to the console directly by using the global print() function. print() can handle a variable amount of arguments, which are then printed to the console in one line, separated by one white space each:

print('Hello', 'World');

Values other than strings can be passed to print() and are converted to strings on the fly:

for (var i = 1; i <= 10; i++) {
	print('We are looping', i, 'times');
}

The console is a convenient way for quickly outputing values while working on scripts, but is maybe not the best way to report values back to an end user. For such cases, we recommend using the Dialog.alert(message) function.

Alert Dialog

The Dialog.alert(message) offers a simple way to display an alert dialog. It receives the string to display as one argument.

Dialog.alert('I always thought something was fundamentally wrong with the universe.');

The variant Dialog.alert(title, message) additionally allows the definition of a title for the dialog. The following example also illustrates how quotes witin strings need to be encoded with a preceding backslash (\) in order to not accidentally mark the end of the string.

Dialog.alert('Don\'t Panic', 'As long as you know where your towel is.');

Confirm Dialog

Using the Dialog.confirm(message) function, we can ask the user a simple question that he can either confirm or cancel. If confirmed, the function returns true, otherwise false. The result can then be used to make further decisions.

var decision = Dialog.confirm('Do you want to know the Answer to the Ultimate Question of Life, the Universe, and Everything?');
if (decision) {
	Dialog.alert('The Answer is 42, what was the question again?');
}

If this dialog is confirmed, the second dialog is displayed, unveiling the answer:

Prompt Dialog

The confirm dialog allows to ask the user simple yes/no kind of questions. For anything more complex than that, Scriptographer provides the freely configurable prompt dialog. Prompt dialogs are displayed through the Dialog.prompt(title, items) function. Just like with Dialog.alert(title, message), title defines the title of the dialog window to be displayed. The flexibility lies in items, an object containing descriptions of multiple rows of different kind of dialog components, to be displayed in the dialog. But let's begin with a simple example:

// First we define the dialog components
var components = {
	firstName: { type: 'string', label: 'First Name' },
	lastName: { type: 'string', label: 'Last Name' },
	email: { type: 'string', label: 'Email Address' }
};

// Now we bring up the dialog
var values = Dialog.prompt('Please Enter Your Contact Details', components);

if (values) {
	// Results were returned, so let's say hello to the user:
	Dialog.alert('Hello ' + values.firstName + ' ' + values.lastName
 		+ ', your email address is: ' + values.email);
}

This brings up the following dialog, with empty text fields that can be filled in by the user:

When confirmed, the dialog returns the entered values as an object that we keep in the values variable:

var values = Dialog.prompt('Please Enter Your Contact Details', components);

The results then can be looked up in the values variable under the same names as used to define the dialog components. But if the dialog was canceled, nothing is returned. So we always need to check wether the values actually contains values, before doing anything with them:

if (values) {
	// values contains the results, so let's say hello to the user:
	Dialog.alert('Hello ' + values.firstName + ' ' + values.lastName
 		+ ', your email address is: ' + values.email);
}

Now let's look at the definition of dialog components in a bit more detail:

var components = {
	firstName: { type: 'string', label: 'First Name' },
	lastName: { type: 'string', label: 'Last Name' },
	email: { type: 'string', label: 'Email Address' }
};

We are defining an object with the three properties firstName, lastName and email.

Please note:

The commas between the properties are necessary, except for after the last one. If they are omitted, Scriptographer's JavaScript engine will display an error.

Each of the defined dialog components are nested objects themselves and look like this:

	firstName: { type: 'string', label: 'First Name' },

With this simple object we are telling Scriptographer to produce an input field for entering a String value, so a line of text. We are also telling it to display a label next to it that says 'First Name:'. The ':' is automatically added by Scriptographer.

The prompt dialog supports many different types of input fields, not only text lines. The possible values for type are: 'string', 'number', 'text', 'slider', 'checkbox', 'button', 'list' and 'color'. Here we go through them one by one and describe the additional properties that each type can receive. In addition to the default properties type and label, all dialog components support the property value, defining the value to initially be displayed and also returned if it remains unchanged.

String Components

As we saw in the example above, string components are edit fields that contain one line of text. In addition to the standard properties, they can define the length property, specifying the field's width in average amount of characters:

var components = {
	string: {
		type: 'string', label: 'String', length: 40,
		value: 'This is a long string that needs more space than usual'
	}
};
var values = Dialog.prompt('String Component', components);

Number Components

Number components work in the same way as string components, but only accept numerical values. They support these additional properties:

  • range defines the range for the numeric value as an array in the form: range: [min, max]. The first element in the array defines the allowed minimum amount, the second the maximum amount, both are included in the range.
  • steppers activates little stepping arrows on the side of the field if set to true.
  • increment The amount the steppers are increasing / decreasing the value. Even when steppers are not activated, the user can still use the up/down arrow keys to step by the amount defined by increment.
  • units The units to be displayed behind the value. These values are allowed: 'none', 'point', 'inch', 'millimeter', 'centimeter', 'pica', 'percent', 'degree' and 'pixel'
  • fractionDigits The amount of digits after the decimal point. If finer grained values are entered, they are rounded to the next allowed number. The default is 3.
var components = { 
	number1: { 
		type: 'number', label: 'Number 1', 
		value: 1 
	}, 
	number2: { 
		type: 'number', label: 'Number 2', 
		value: 2.5,  fractionDigits: 1, increment: 0.5
	}, 
	number3: { 
		type: 'number', label: 'Number 3', 
		value: 5, range: [0, 10], 
		steppers: true, units: 'point' 
	}, 
}; 
var values = Dialog.prompt('Number Component', components);

Text Components

Text components are similar to string components but support multiple lines of text. The dimensions of the text area are defined by rows and columns instead of length:

  • rows defines the amount of visible lines of text in the text area. Due to a bug in Illustrator's GUI, values bellow 6 cause problems with scrollbars on Macintosh. The default is 6.
  • columns defines the average amount of characters per line visible in the text area. The default is 32.
var components = {
	text: { 
		type: 'text', label: 'Text', 
		value: 'This is a text\nwith multiple lines',
		rows: 6, columns: 30
	}
}; 
var values = Dialog.prompt('Text Item', components);

Slider Components

Sliders are horizontal controls that allow the user to choose a value in a given range by dragging the slider handle. They can be configured by a row of properties:

  • width defines the width of the slider in pixels. The default is 160px.
  • range defines the range for the numeric value as an array in the form: range: [min, max]. The first element in the array defines the allowed minimum amount, the second the maximum amount, both are included in the range.
  • increment The steps between allowed values, quantisizing the range.
var components = {
	slider: { 
		type: 'slider', label: 'Slider',
		value: 5, range: [0, 25]
	}
};
var values = Dialog.prompt('Slider Item', components);

Checkbox Components

A checkbox is a control that can be checked or unchecked, toggling a boolean user between true and false. It does not offer any additional properties.

var components = {
	checkbox: { 
		type: 'checkbox', label: 'Checkbox',
		value: true
	}
};
var values = Dialog.prompt('Checkbox Item', components);

Button Components

Button components appear as normal buttons inside the prompt dialog, with value as text that appears on the button. When clicked, they fire a onClick event, which can be handled by a provided function.

var components = {
	button: { 
		type: 'button', label: 'Button',
		value:'Click Me',
		onClick: function() {
			print('You clicked me!');
		}
	}
};
var values = Dialog.prompt('Button Item', components);

List Components

List components appear as popup menus, offering a multitude of options of which one can be chosen. The value property defines the currently selection option, whereas options defines the selection of values to choose from as an Array.

var components = { 
	button: { 
		type: 'list', label: 'List', 
		value:'Two',
		options: ['One', 'Two', 'Three']
	} 
}; 
var values = Dialog.prompt('List Item', components);

Color Components

Color components are small color buttons that bring up a color chooser when clicked. Just like in the rest o Scriptogrpaher, the color color value can either be provided as a Color object or a String either defining a color name or a color hex value.

var components = { 
	color: { 
		type: 'color', label: 'Color', 
		value:'#ff0000'
	} 
}; 
var values = Dialog.prompt('Color Item', components);

As a final example here a simple script that asks the user a color and a radius and then creates a circular path with the given radius and filled with the chosen color.

var components =  { 
	radius: { 
		type: 'number', label: 'Radius', 
		value: 50, range: [10, 100], 
		units: 'point', length: 6
	},
	color: { 
		type: 'color', label: 'Color', 
		value: new RGBColor(1, 0.5, 0) // Orange by default
	}
};
var values = Dialog.prompt('Create Colored Circle', components);
// values is only defined if the user clicked 'OK',
// so only produce the path in that case.

if (values) {
	var myCircle = new Path.Circle(
		new Point(100, 100), values.radius);
	myCircle.fillColor = values.color;
}

Separating Values from Component Definitions

In all the above examples, we have defined the default values inside the dialog components. For real world use, it is often far clearer and more useful to define them in a separate values object, which is then passed to Dialog.prompt(title, items) as a third parameter. We therefore can rewrite the above example without changing the functionality:

// Define the default values, to be passed to Dialog.prompt()
var values = {
	radius: 50,
	color: new RGBColor(1, 0.5, 0) // Orange by default
};

// Define the dialog components
var components =  { 
	radius: { 
		type: 'number', label: 'Radius', 
		range: [10, 100], length: 6
		units: 'point'
	},
	color: { 
		type: 'color', label: 'Color'
	}
};

// Pass the default values to Dialog.prompt, and put
// the result back into vallues:
values = Dialog.prompt('Create Colored Circle', components, values);

// values is now only defined if the user clicked 'OK',
// so only produce the path in that case.
if (values) {
	var myCircle = new Path.Circle(
		new Point(100, 100), values.radius);
	myCircle.fillColor = values.color;
}

Floating Palettes

Prompt dialogs are useful for scripts that need to ask the user one or a multitude of values at one time and then can work with these. But they are not suitable for scripts that would like to continuously offer options, for example tool scripts that allow the customization of the tool's behavior.

For such scripts, Scriptographer offers the Palette type, which is sort of a twin of the Dialog.prompt(title, item) function, but produced a so called modeless floating palette window. They both share the way components and values are defined. But palette windows are created as objects, as this example illustrates. We keep the functionality from the last prompt dialog example but turn into a Circle Factory palette that has an additional button which when clicked produces a circle with the chosen attributes each time it is clicked, at a random position across the document.

Please note:

The values object that is passed to the new Palette(title, items, values) constructor is continuously updated whenever the user changes values in the palette, so the script can simply keep refering to that for the correct values.

// Define the default values, to be passed to the
// new Palette() constructor
var values = {
	radius: 50,
	color: new RGBColor(1, 0.5, 0)
};

// Define the palette components
var components = {
	radius: { 
		type: 'number', label: 'Circle Radius', 
		range: [10, 100], length: 6,
		units: 'point'
	},
	color: { 
		type: 'color', label: 'Circle Color'
	},

	// The newly added create button
	create: {
		type: 'button', value: 'Create',
		onClick: function() {
			// We use similar functionality as in the previous
			// example, with the added randomized center point.
			var myCircle = new Path.Circle(
				Point.random() * document.size, values.radius);
			myCircle.fillColor = values.color;
		}
	}
};

// Now create the palette window
var palette = new Palette('Circle Factory', components, values);
Please note:

We define the center point of the circle by using the Point.random() functions. It returns a new point object with random values for both coordinates in the range between 0 and 1. By multiplying this with the document.size, we get a random point that lies in the area of the entire document:

	var center = Point.random() * document.size;

Mouse Tool with Palette Window

The next example illustrates the way palette windows can be combined with mouse tools. Let's make a simple drawing tool of which the tool.distanceThreshold can be changed in a palette. To learn more about mouse tools, refer to the Creating Mouse Tools tutorial.

var values = { 
	threshold: 0
};

var components = { 
    threshold: {
		type: 'number', label: 'Distance Threshold',
		units: 'point'
    }
};

var palette = new Palette('Drawing Tool', components, values);

var path;

function onMouseDown(event) {
	// Update the distanceThreshold each time the mouse button
	// is pressed, by reading it form the values object
	tool.distanceThreshold = values.threshold;

	// Now create the path and add the first point to it.
	path = new Path();
	path.add(event.point);
}

function onMouseDrag(event) {
	path.add(event.point);
}

onChange Handler

As values of palette components can change at any time through user interaction, palette components can define a onChange handler that is called each time their value is changed, similar to the onClick handler introduced earlier for button components. We could rewrite the above example to use such a handler instead:

var components = { 
    threshold: {
		type: 'number', label: 'Distance Threshold',
		units: 'point',
		onChange: function(value) {
			print('Threshold was changed to', value);
			tool.distanceThreshold = value;
		}
    }
};

This would then simplify the onMouseDown handler:

function onMouseDown(event) {
	path = new Path();
	path.add(event.point);
}

The entire code of the new version then looks like this:

var components = { 
    threshold: {
		type: 'number', label: 'Distance Threshold',
		units: 'point', value: 0,
		onChange: function(value) {
			print('Threshold was changed to', value);
			tool.distanceThreshold = value;
		}
    }
};

var palette = new Palette('Drawing Tool', components);

var path;

function onMouseDown(event) {
	path = new Path();
	path.add(event.point);
}

function onMouseDrag(event) {
	path.add(event.point);
}