FilePicker
A FilePicker is the user-facing system interface for selecting files or folders. The items chosen by the user are returned to the Omni Automation script as an array of URLs to the chosen items. The FilePicker class is supported by all Omni Automation applications on both iOS and macOS.
Constructors
An instance of the FilePicker class is created using the standard new constructor without any initial provided parameters.
new FilePicker()-->(FilePicker) Returns a new FilePicker with default settings.
New FilePicker Instance
picker = new FilePicker()
Instance Properties
Control of the selection options for a FilePicker instance is accomplished by setting the values of the following properties:
folders (Boolean) If true, then folders may be selected, but not files. In this case, types is ignored. Defaults to false.
message (String)(macOS only) A message to display describing what files are being picked. This is currently only supported on macOS.
multiple (Boolean) If true, then multiple files may be selected. Defaults to false.
types (Array of FileType or null) The file types that will be allowed. If null, all file types will be allowed. Defaults to null.
For example, the following script will create a FilePicker instance that will only allow the selection of a single text file:
Set Properties of New FilePicker
picker = new FilePicker()
picker.folders = false
picker.multiple = false
picker.types = [FileType.plainText]
Instance Methods
Once an instance of the FilePicker class has been created, it can be displayed to the user by calling the show() method on the instance.
show() --> (Promise) Presents the system file selection interface to the user, allowing them to choose one or more files of the given types. The returned Promise will yield the chosen URLs on success. If the user cancels chosing, the Promise will be rejected. Note that even when picking a single file or folder, the result will always be an array of URL objects.
Create and Display FilePicker
picker = new FilePicker()
picker.folders = false
picker.multiple = false
picker.types = [FileType.plainText]
pickerPromise = picker.show()
Depending upon which operating system is used by the device running the script, the file picker will be presented as either a sheet (macOS 10.5) or an overlay dialog (iOS, macOS 11+).
Alternate Plain Text Type
aType = new FileType("public.plain-text")
picker = new FilePicker()
picker.folders = false
picker.multiple = false
picker.types = [aType]
pickerPromise = picker.show()
Processing the FilePicker Results
When a file picker dialog is displayed, a JavaScript Promise object is returned to the script. This promise represents either the completion or cancellation of the user’s interaction with the file picker dialog, and if successful will contain an array of URL references to the items chosen by the user.
The Promise object can be processed by the script with standalone functions that process its success or cancelation, or through the inline appending of the then(…) function of the Promise class to the show() command (of the FilePicker class) to act as a receiver for the passed promise object.
In the following example of the inline technique, the call-back function within the then(…) method is passed the result of the successful completion of the picker dialog, which is an array of URLs to the chosen items. Note that the successful picker result is always an array of URL objects even if only a single item was chosen.
Choose and Import Text from File
picker = new FilePicker()
picker.folders = false
picker.multiple = false
picker.types = [FileType.plainText]
picker.show().then(urlsArray => {
aURL = urlsArray[0]
aURL.fetch(data => {
console.log(data.toString())
})
})
Optionally, you may call the then() function on the stored Promise object to retrieve the chosen file(s), and to respond with other actions should the user choose to cancel the FilePicker:
FilePicker (Explicit Promise)
picker = new FilePicker()
picker.folders = false
picker.multiple = false
picker.types = [FileType.plainText]
pickerPromise = picker.show()
pickerPromise.then(function(urlsArray){
fileURL = urlsArray[0]
console.log(fileURL.string)
})
pickerPromise.catch(err =>{
console.log("picker cancelled", err.message)
})
Another approach is wrap the FilePicker code in an asynchronous function (async) and precede the call to show the FilePicker with the term: await
FilePicker (Asynchronous Promise)
(async () => {
try {
picker = new FilePicker()
picker.folders = false
picker.multiple = false
picker.types = [FileType.plainText]
fileURL = await picker.show()
console.log(fileURL.string)
}
catch(err){
if(!err.causedByUserCancelling){
new Alert(err.name, err.message).show()
}
}
})();
Here’s an example of using a file picker to select an open an existing OmniGraffle file and add a new canvas to it:
Choose and Open OmniGraffle Document
var picker = new FilePicker()
picker.folders = false
picker.multiple = false
aFileType = new FileType("com.omnigroup.omnigraffle.graffle")
picker.types = [aFileType]
pickerPromise = picker.show()
pickerPromise.then(function(urlsArray){
fileURL = urlsArray[0]
app.openDocument(
document,
fileURL,
function(doc,wasOpen){
cnvs = doc.portfolio.addCanvas()
}
)
})
An Alternative Method
JavaScript also offers the ability to execute the FilePicker asynchronously, meaning that the statement that invokes the FilePicker can wait until the user has completed selecting one or more files before continuing in the host script. For this to occur, the FilePicker code must be placed within an asynchronous function (marked with the term async) with the FilePicker call preceded with the term: await The resulting array of URLs will be returned as the result of the statement.
Here is an asynchronous version of the previous script example:
The FilePicker (Asynchronous)
(async () => {
try {
picker = new FilePicker()
picker.folders = false
picker.multiple = false
aFileType = new FileType("com.omnigroup.omnigraffle.graffle")
picker.types = [aFileType]
urlsArray = await picker.show()
fileURL = urlsArray[0]
app.openDocument(
document,
fileURL,
function(doc,wasOpen){
cnvs = doc.portfolio.addCanvas()
}
)
}
catch(err){
if(!err.causedByUserCancelling){
new Alert(err.name, err.message).show()
}
}
})();
Create Image Data URL
This script example will create an HTML image data URL using the image data of the chosen PNG file. Image Data URLs do not link to files, but instead include the image data expressed as a base64 string.
The resulting image data URL will be logged to the console and copied to the clipboard, ready to paste into HTML content. IMPORTANT: Always choose VERY SMALL images, and be sure to set the values of the image width and height style properties!
<-- Here is an image displayed using an image data URL!
Create Image Data URL from Chosen Image
picker = new FilePicker()
picker.folders = false
picker.multiple = false
picker.types = [TypeIdentifier.png]
picker.show().then(function(urlsArray){
aURL = urlsArray[0]
aURL.fetch(function(data){
imageURL = "<img style=\"width:auto;height:auto;\" src=\"data:image/png;base64," + data.toBase64() + "\">"
console.log(imageURL)
Pasteboard.general.string = imageURL
new Alert("COMPLETED","URL code is on the clipboard.").show()
})
})
Folder Picker
To create a FilePicker that selects a chosen folder, include the UTI for a folder AND set the value of the picker’s folders property to true. Here is an asynchronous example script that opens the selected folder:
Folder Picker
(async () => {
try {
folderUTI = new TypeIdentifier("public.folder")
picker = new FilePicker()
picker.multiple = false
picker.folders = true
picker.types = [folderUTI]
urls = await picker.show()
urls[0].open()
}
catch(err){
if(!err.causedByUserCancelling){
new Alert(err.name, err.message).show()
}
}
})();
Using FilePicker with Plug-Ins
The use of a file picker can greatly enhance the usefulness of Omni Automation actions designed to import and integrate data or files into documents. For example, a file picker provides an excellent way for a user to select a text file whose contents are to be imported into a selected container or shape in OmniGraffle.
You can use the following example action as a template for integration of a file picker into an Omni Automation action.
Import Text into Shape
/*{
"type": "action",
"targets": ["omnigraffle"],
"author": "Otto Automator",
"version": "1.0",
"identifier": "com.omni-automation.og.import-text-into-shape",
"description": "Imports text from the chosen text file into the selected shape.",
"label": "Import Text into Shape",
"shortLabel": "Import Text",
"paletteLabel": "Import Text",
"image": "square.and.arrow.down.fill"
}*/
(() => {
const action = new PlugIn.Action(async function(selection, sender){
selectedShape = selection.solids[0]
// CONSTRUCT THE PICKER
picker = new FilePicker()
picker.folders = false
picker.multiple = false
picker.types = [FileType.plainText]
// SHOW THE PICKER AND RETURN JAVASCRIPT PROMISE
urlsArray = await picker.show()
// PROCESS THE PICKER RESULTS
fileURL = urlsArray[0]
// IMPORT DATA FROM CHOSEN FILE
fileURL.fetch(function(data) {
selectedShape.text = data.toString()
})
})
// VALIDATE DOCUMENT SELECTION
action.validate = function(selection, sender){
return (selection.solids.length == 1 ? true:false)
}
return action
})();
Picker Prompt
In recent Omni application updates, the message property has been added to the FilePicker class to provide the ability to display a prompt in the macOS file picker dialog. Since iOS does not support the inclusion of a prompt in the iOS file picker, the following script example shows how to use an alert to provide similar prompt functionality on iOS:
Prompting the User
(async () => {
try {
picker = new FilePicker()
picker.folders = false
picker.multiple = false
picker.types = [FileType.plainText]
if(app.platformName === "iOS"){
msg = "Tap ”Continue” and choose a text file from the forthcoming file picker:"
alert = new Alert("Select File", msg)
alert.addOption("Continue")
await alert.show()
} else if (app.platformName === "macOS"){
picker.message = "Choose the text file to import:"
}
urls = await picker.show()
urls[0].fetch(function(data){
console.log(data.toString())
})
}
catch(err){
if(!err.causedByUserCancelling){
new Alert(err.name, err.message).show()
}
}
})();
JavaScript Promises and the FilePicker
A JavaScript Promise is standard built-in object of the JavaScript language. The JavaScript Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.
The use of JavaScript Promises with Omni Automation, and the FilePicker class in particular, enables a simpler design for your scripts and actions that are meant to respond to events, such as the presentation and completion of a file picker dialog.
Instead of writing complex functions containing multiple call-backs, using JavaScript promises, scripts can divided into functions that are individually called when file picker dialogs have been either approved or dismissed.
Promises are generated when a file picker is displayed using the show(…) method of the FilePicker class. The resulting promise is stored in a variable so that its functions for responding to the dialog approval or cancelation can be identified and isolated in the script.
Run the following example script and view the scripting console after closing the file picker dialog to see the results of the promise.
FilePicker with Explicit Promise
// FILEPICKER INSTANCE CREATED AND PROPERTIES SET
picker = new FilePicker()
picker.folders = false
picker.multiple = false
picker.types = [FileType.plainText]
// PICKER PROMISE IS CREATED AND STORED WHEN PICKER IS DISPLAYED
pickerPromise = picker.show()
// PROMISE FUNCTION CALLED UPON PICKER APPROVAL
pickerPromise.then(urlsArray => {
aURL = urlsArray[0]
// processing statements using url(s)
console.log(aURL)
})
// PROMISE FUNCTION CALLED UPON PICKER CANCELLATION
pickerPromise.catch(err => {
console.log("form cancelled", err.message)
})
Inline Promise Processing
As shown in the example above, the result of using the show(…) method to display a file picker dialog is a JavaScript promise object, which can then be processed using the then(…) or catch(…) functions depending on whether the dialog was approved or canceled.
If processing a canceled picker is not necessary for your script, you can streamline the script coding by appending the then(…) function to the show(…) function as shown in the example below. Functionally, this technique is the same as processing a promise object stored in a variable. The callback function within the then(…) function still receives the array of URLs chosen by the user in the file picker dialog.
Pick and Process
picker = new FilePicker()
picker.folders = false
picker.multiple = false
picker.types = [FileType.plainText]
picker.show().then(function(urlsArray){
aURL = urlsArray[0]
aURL.fetch(function(data){
console.log(data.toString())
})
})
Asynchronous Promise Processing
The example uses await statements within an asynchronous wrapper rather than explicit Promise blocks for implementing the file picker and the retrieval of file data.
Note that the FilePicker code is placed within an asynchronous (async) function wrapper, and the call to show the FilePicker is preceded by the term: await
The resulting array of URLs will be passed to the declared variable: urlsArray
Asynchronous FilePicker
(async () => {
try {
picker = new FilePicker()
picker.folders = false
picker.multiple = false
picker.types = [FileType.plainText]
urlsArray = await picker.show()
urlsArray[0].fetch(function(data){
console.log(data.toString())
})
}
catch(err){
if(!err.causedByUserCancelling){
new Alert(err.name, err.message).show()
}
}
})();
Promise Documentation
JavaScript Promises offer a high degree of customization beyond the basic use with forms shown here. Detailed information regarding JavaScript Promises is available at:
- Omni Automation Plug-In Forms and Promises
- Mozilla Developer Documentation
- Google Developers
- Promise States and Fates
Using URL.FetchRequest to Read Chosen File
The example uses await statements within an asynchronous wrapper rather than explicit Promise blocks for implementing the file picker and the retrieval of file data.
The script uses the fetch() function with the URL.FetchRequest class to asynchronously read the contents of a file, rather than using the fetch() function of the URL class:
Read Contents of Text File
(async () => {
try {
picker = new FilePicker()
picker.folders = false
picker.multiple = false
picker.types = [FileType.plainText]
urls = await picker.show()
fileRequest = new URL.FetchRequest()
fileRequest.url = urls[0]
fileContents = await fileRequest.fetch().then(file => file.bodyString)
Pasteboard.general.string = fileContents
}
catch(err){
if(!err.causedByUserCancelling){
new Alert(err.name, err.message).show()
}
}
})();