Phrame.js documentation

Documentation updated: 06-26-2013.

Download PHRAME.JS v0.01 Fork on GitHub

Index

  1. Introduction
  2. MVC explained
  3. Structure
  4. Getting started
    1. Adding the framework to Photoshop, InDesign or Illustrator
    2. Remotely hosting Phrame.js
  5. Tutorials
  6. License

Introduction

Welcome to Phrame.js, a JavaScript/ExtendScript* MVC framework for Photoshop, InDesign and Illustrator scripting. The purpose of Phrame.js is making your Photoshop, InDesign and Illustrator scripting modular, maintainable and updatable. Remember that this framework is intended for medium to large scripts. Scripts that are only a hundred lines long will probably not benefit from the scalability of Phrame.js.

As a Photoshop, InDesign or Illustrator scripter it can be tedious to write scripts. All your code is crammed into one file and it doesn't take long before this file gets out of control. Phrame.js tries to solve that problem by splitting up your application into multiple elegant little pieces.

Throughout this documentation it is possible that you'll encounter things you have never heard of before. Don't be put off by the unfamiliar sounding terminology. Phrame.js is very easy to learn, but requires certain structures and conventions in order to stay scalable.

If you ever find yourself having a hard time wrapping your head around Phrame.js, remember one thing: we have walked the exact same path as you and even though the path is quite rocky and steep sometimes, there is in fact good news: there will be enlightment at the end as long as you keep going forward.

That being said, we hope you'll enjoy using the Phrame.js framework. Happy scripting and remember, search engines are your friend!

* It must be noted that this documentation refers to JavaScript, although Adobe scripting is technically done by writing ExtendScript, an extension of the ECMA-script created by Adobe.

MVC explained

Phrame.js is based on the MVC model, which means the code is categorized in three parts: the model, the view and the controller. Don't be alarmed if this much acclaimed acronym doesn't sound familiar. It's a model that is very easy to use once you understand the convention.

An MVC framework will help you keep your program structured. There are plenty of good articles out there that explain the MVC model in depth, so we'll just briefly discuss the model and explain how it fits into Photoshop, InDesign and Illustrator scripting and Phrame.js so we can move on to how to use it .

The MVC model separates an application in three parts: parts that are shown on the screen (= view), parts that do data manipulation (= model) and parts that decide which methods from the model and view that need to be called based on the certain values (controller).

Here's a very basic example. The user executes a script. This script calls up the controller, which acts like a traffic agent, it controls what has to be done. The controller calls the prompt method from the view. This view method shows the user a prompt with input fields. The user checks a few buttons and fills out his name. After he's done, the data gets sent back to the controller. The script requires the data to be inserted into the database first, so the controller gives the data to a method in the model. This model method inserts the received data into the database and notifies the controller that the record was successfully added to the database. On that cue, the controller addresses the view again, this time calling a method that will create a text layer containing the text the user has just filled in. And that is the cycle of a very brief MVC application.

Structure

These next paragraphs are not ordered by the application folder structure but by the application execution order. The folder structure is merely used as an indicator of where the files are located locally. This might be confusing at first, but is easier to clarify the inner workings of the framework because it's the execution order and not the folder structure that is of importance in an MVC framework.

Phrame.js: the core

As you might have noticed, there is only one .js file in the Phrame.js framework. This is because Photoshop, InDesign and Illustrator automatically index all the .js or .jsx files that are in the script folder.

The Phrame.js file acts as a single point of entry, meaning that all the other files are loaded through the Phrame.js file. All the other files containing JavaScript have the .jsph extension, preventing them from being indexed by Photoshop, InDesign or Illustrator. This .jsph extension is inherent to the Phrame.js framework.

The Phrame.js file does two things. First of all it sets the pseudo constants, which are, besides the Phrame object, the only global variables being used in the framework. They are called pseudo constants because JavaScript does not support constants. So beware, it is possible to overwrite them.

// SET GLOBAL VARIABLES. 
var APP_NAME		=	'Phrame.js';	//remove: Set the name of the application (case sensitive). This name is the same as the folder and application name. Default: Phrame.js
var APP_PATH		=	app.path + '/' + localize("$$$/ScriptingSupport/InstalledScripts=Presets/Scripts") + '/' + APP_NAME + '/';
var APP_EXT		=	'.jsph';

// Beware: these folder pseudo constants are automatically changed by the bootstrap for easier use.
var FOLDER_AP		=	'application';
var FOLDER_SY		=	'system';
var FOLDER_DB		=	'database';
var FOLDER_AS		=	'assets';

var DB_EXT		=	'.json';
var DB_SUF		=	'_db';
var DB_LOAD		=	false;

These constants are used for configuring your framework, but the only constant that is of any importance for getting started is APP_NAME. This constant is used to set the path to the current folder.

You can change the Phrame.js file and folder name to the anything you want, for instance the name of your script. If you change the file and folder name, make sure you also change the APP_NAME constant to that exact same value.

The system folder

bootstrap.jsph

The bootstrap does all the heavy lifting by traversing the Phrame.js folder and loading all the necessary .jsph files in the right order. That way only the Phrame.js shows up in the Photoshop, InDesign and Illustrator script list. All the other files will remain hidden as long as they have the required .jsph extension. It also creates the global Phrame object, to which the entire framework will be appended.

library/library.jsph

Accessible through Phrame.library. The library contains common helping methods that can be used in any part of your application. These range from quickly getting the selected radio button values from a prompt to alerting the content of an object for quick debugging.

For a complete overview you will have to open the file and skim through it for now ;)

model.jsph

Accessible through Phrame.model. This file is the common model object to which all your application model methods will be appended. It also loads the database records into the Phrame.database object if the DB_LOAD constant in the Phrame.js file is set to true.

view.jsph

Accessible through Phrame.view. This file is the common view object to which all your application view methods will be appended.

controller.jsph

Accessible through Phrame.controller. This file is the common controller object to which all your application controller methods will be appended.

The database folder

Accessible through Phrame.database. The database object is a very rudimentary, non relational, local data storage system based on JSON, a lightweight data-interchange format. All the files in this folder are loaded automatically if the DB_LOAD is set to true. It's advisible to only put json files in this folder that are necessary for the application to run, avoiding unnessecary loading of database files.

The database is stored locally and can't be linked directly to external database management systems because Photoshop, InDesign and Illustrator don't allow external request. You could set up an API-like system which manipulates (and more importanly perhaps, makes backups of) these local database files externally, but this requires external scripting beyond the scope of Phrame.js.

Because of these limitations, each time you change a record in your database table, the whole table gets updated, not just that specific record. This makes the current Phrame.js database model very suspectible to errors and it can easily cause the loss of your entire local database file.

With the exception of functions, you can store any datatype in these records. Experiment adequately to figure out what the database model allows and disallows before implementing it into your script. Use this model with caution and make sure you apply the following rules meticulously.

Database file rules

The database file name

Every database file that needs to be loaded on runtime needs to be followed by the DB_SUF suffix (default: _db) and requires the DB_EXT (default: .json) extension. The part before the suffix is the name of the table and can be any string you want. For example, a database containing designer chairs should be named as follows: designerchairs_db.json.

The database file content

Phrame.js automatically appends the database file content to the Phrame.database object. The part before the suffix of the file name will be the name of the table.

The content of this file should be a valid JSON string, which is the exact same notation as a JavaScript object. Here is an example that shows what's inside the designerchairs_db.json file.

{
	"0"	:	{
				"chair"		:	"Barcelona chair",
				"caption"	:	"Barcelona chair designed by the German-American architect Ludwig Mies van der Rohe.",
				"filename"	:	"barcelona-chair.jpg"
			},
	"1"	:	{
				"chair"		:	"Ball chair",
				"filename"	:	"ball-chair.jpg",
				"caption"	:	"Ball chair designed by the Finnish designer Eero Aarnio."
			}
}

Make sure you put every key and value between double quotes or it will not parse as a valid JSON object.

Accessing a record in a database

In order to get access to the entire database you can apply the getRecord() method on any database object. This method will return an array containing all the records in the database. You can loop over the returned array like you would with any array in JavaScript.

var designerchairs	=	Phrame.database.designerchairs.getRecord();

for (var i = 0; i < designerchairs.length; i++)
{
	alert(designerchairs[i]['filename']);
}

// Output: barcelona-chair.jpg 
// Output: ball-chair.jpg

You can also pass an numerical index to the getRecord() method which will return the record located at that index of the database.

var designerchair	=	Phrame.database.designerchairs.getRecord(1);

alert(designerchair['filename']);

// Output: ball-chair.jpg

Updating a record in a database

In order to update a record in a database you can apply the updateRecord() method on any database object. This method will return true if the update succeeded or false if the update failed.

This method requires a numerical index as the first argument and an object containg the key-value pair that need to be updated. It is also possible to augment the original record with additional fields.

var designerchairsUpdate	=	Phrame.database.designerchairs.updateRecord(1, {'caption':'Updated caption for ball chair.', 'thumb-filename':'thumb-ball-chair.jpg'});

if(designerchairsUpdate)
{
	var ballChair	=	Phrame.database.designerchairs.getRecord(1)
	
	alert(ballChair['caption']);
	alert(ballChair['thumb-filename']);
}

// Output: Updated caption for ball chair.
// Output: thumb-ball-chair.jpg

Inserting a record into a database

In order to insert a record in a database you can apply the updateRecord() method on any database object. This method will return the index of the newly inserted record if the insert succeeded or false if the insert failed.

This method requires an object with the key-value pairs or a regular array as an argument, which will be used be inserted into the database.

var designerchairsInsert	=	Phrame.database.designerchairs.insertRecord({'chair':'Red and blue chair.', 'filename':'red-and-blue-chair.jpg', 'caption':'Red and blue chair designed by the Dutch furniture designer Gerrit Rietveld.'});

if(designerchairsInsert !== 'false')
{
	var insertedChair	=	Phrame.database.designerchairs.getRecord(designerchairsInsert)
	
	alert(insertedChair['chair']);
	alert(insertedChair['thumb-filename']);
	alert(insertedChair['caption']);
}

// Output: Red and blue chair.
// Output: red-and-blue-chair.jpg
// Output: Red and blue chair designed by the Dutch furniture designer Gerrit Rietveld.

It is also possible to insert a record on a specific index by providing an additional index as argument.

var designerchairsInsert	=	Phrame.database.designerchairs.insertRecord({'chair':'Model B3.', 'filename':'model-b3.jpg', 'caption':'Model B3, also know as the Wassily Chair designed by the Hungarian furniture designer Marcel Breuer.'}, 10);

if(designerchairsInsert !== 'false')
{
	var insertedChair	=	Phrame.database.designerchairs.getRecord(10)
	
	alert(insertedChair['chair']);
	alert(insertedChair['thumb-filename']);
	alert(insertedChair['caption']);
}

// Output: Model B3.
// Output: model-b3.jpg
// Output: Model B3, also know as the Wassily Chair designed by the Hungarian furniture designer Marcel Breuer.

Remember that inserting a record on a specific index can possibly cause conflicts if you loop over the database array based on it's length.

Deleting a record in a database

In order to delete a record from a database you can apply the deleteRecord() method on any database object. This method will return the deleted record if the deletion succeeded or false if the deletion failed.

This method requires a numeric index as argument.

var deletedDesignerChair	=	Phrame.database.designerchairs.deleteRecord(10);

if(deletedDesignerChair !== 'false')
{
	var insertedChair	=	Phrame.database.designerchairs.insertRecord(deletedDesignerChair, 3);
	
	if (insertedChair !== false)
	{
		var currentChair	=	Phrame.database.designerchairs.getRecord(3);
		
		alert(currentChair);
		alert(currentChair['chair']);
		alert(currentChair['thumb-filename']);
		alert(currentChair['caption']);
	}	
}

// Output: Red and blue chair.
// Output: red-and-blue-chair.jpg
// Output: Red and blue chair designed by the Dutch furniture designer Gerrit Rietveld.

Remember that deleting a record can possibly cause conflicts if you loop over the database array based on it's length.

The application folder

This is where you will spend most of your time writing your actual application.

model/view/controller folder

For information on how to use these folders adequately, read through the MVC explained section of this documentation first.

Application naming convention

These folders contain all the class files that are inherent to your application. These files are automatically appended to the Phrame object based on their file name, which is why the following file naming convention must be applied meticulously.

  1. The file name has to start with the name of your class
  2. The first character of your class name needs to be in uppercase.
  3. The class name can only contain alphabetical characters. Use CamelCase to separate words, do not use underscores.
  4. The last character of the class name must be followed by an underscore.
  5. The file name needs to end with the name of the object your appending your class to: model, view or controller.
  6. The file needs to have the .jsph extension.

For example, a file in the view folder would have the following file name structure: DesignerChair_view.jsph and is directly accessible by calling the Phrame.view.DesignerChair object.

Application code convention

Closures are an essential part of the Phrame.js framework, meaning that all your application files will have to be an anonymous closure. There is no need to assign the closure to a variable because this is done automatically by Phrame.js. The part before the underscore of the file name will become the object name through which the closure will be accessible.

// 	File	:	DesignerChair_view.jsph 
// 	Method	:	Phrame.view.DesignerChair
//	-------------------------------------------------------------

(function() { // Closure
	
	// Private properties and methods
	var designerChairs;	
	
	// Public properties and methods
	return {
				'constructor'	:	function() { // Populate the designerChairs private variable with the designer chair records from the db_designerchairs.json file
									
										designerChairs	=	Phrame.database.designerchairs;
									},
									
				'offerChair'	:	function(key) { // can be called from anywhere inside the public methods of this closure and anywhere outside this closure
									
										// Define variables
										var w, inputTextLabel; 	
										
										// Create prompt
										w	=	new Window ('dialog', 'Getting started with Phrame.js');
																		
										// Create text label
										inputTextLabel	=	w.add ('statictext', undefined, 'Hello there and greetings from the closure.\n\rHave a seat in this wonderful ' + designerChairs[key]['chair']);
										
										// Show prompt
										if (w.show() == 1)
										{
											return true;
										}
									}
			}
}());

At the beginning of the file, write two comment lines that clearly state the file name and method that needs to be called in order to invoke the returned methods of the closure. This is not used for application purposes, but serves as structural clarification.

The returned constructor object is reserved by Phrame.js and will be executed automatically after the entire application code is loaded. The constructor is not mandatory and can be omitted.

The returned methods can be called from anywhere inside the application, for instance the offerChair() method is invoked by calling Phrame.view.DesignerChair.offerChair(1).

Here are some tips for when you are first learning to use closures.

  • Encapsulating your closure function with round brackets is a non-functional way of indicating that the function inside is in fact a closure. Besides aesthetics and readability it doesn't have a function, but it is still advisable to write them.
  • The anonymous function that is being passed to the variable name Phrame.view.DesignerChair should be followed by (). This means the function will be executed on runtime and will only be executed once.
  • The scope of the objects that are defined before the return value is limited to the closure, meaning that only the returned properties and methods are accessible from outside the closure. The only way you can get access to these private properties is by adding a property or method to the returned object that explicitly returns one of these private property values.

globals folder

In this folder you can put .jsph files that have to be appended to the global name space. The big difference with the model/view/controller files is that these globals are not specific for either the model, view or controller and they can be used everywhere in the application.

This folder is suited for:

  • Adding a config file that defines global preferences, for instance:
    // CONFIG FILE
    preferences.rulerUnits  = Units.PIXELS
  • Adding prototypes or methods that do not exist exclusively in either the model, view or controller.

In any case, use this folder sparesly and knowingly.

The assets folder

This folder contains all your assets like image files, text files or other files that are necessary for your application. You can access this folder by using the FOLDER_AS constant, which returns the direct path to the folder:

If you have images in the assets folder that are in a separate folder called designer-chairs you can load them as follows:
app.load(FOLDER_AS + 'designer-chairs/barcelona-chair.jpg');

Getting started

Adding the framework to Photoshop, InDesign or Illustrator

Getting started is fairly easy. Download the framework and place it in the script folder of Photoshop, InDesign or Illustrator. Rename the Phrame.js folder and the Phrame.js file to the name of your application, for instance DesignerChairs for the folder name and DesignerChairs.js for the file name. Then, in the DesignerChairs.js file, change the APP_NAME value to the name of your application: DesignerChairs.

var APP_NAME		=	'DesignerChairs';

You can now start scripting in the application folder.

Remotely hosting Phrame.js

It is possible to host your scripts anywhere outside the script folder, for instance in your Dropbox folder. The only file that needs to be in the script folder is the Phrame.js file, which you should rename to the name of your application.

Assuming you already went through the steps of adding the framework to Photoshop, InDesign and Illustrator, create a remote folder anywhere on your harddrive with the exact same name as your application, in this case DesignerChairs. Open the DesignerChairs.js file which should still be in your script folder and change APP_PATH constant to the right value.

var APP_NAME	=	'DesignerChairs';
var APP_PATH	=	'c:/dropbox/DesignerChairs/';

You can now start working on your application in the c:/dropbox/DesignerChairs/ folder. This way it is also possible to share the same script with multiple users. All you need to do is put the DesignerChairs.js script in the application script folder and point the APP_PATH constant to the shared folder hosting the script.

Tutorials

There are currently no tutorials, but we're working on that.

If you have written your own Phrame.js tutorial, feel free to contact us and we'll add it to the list after review.

License

Phrame.js is an Open Source project and falls under the MIT license, meaning you are free to to use, copy, modify, merge, publish, distribute, sublicense, and/or sell the software.