I’ve recently put a fair bit of work into integrating HTML forms with client-side AppleScripts, so that I can use much more complicated UIs in my scripts. It’s turned out to be an incredibly useful addition to my scripting arsenal, since it gets me away from the nightmare of sequential dialog boxes filled with half a dozen buttons a piece.
The basics are as follows:
Create an applet with a custom location handler (e.g. myscript://)
Create a web form that sends a GET request to that same handler
Process the URL in the applet, and use the form arguments to drive script behavior and/or write to script properties
The first two steps are fairly straightforward, but the parsing of the request is the really difficult part. But here’s the code to handle that:
You can find a full discussion of how it works and a sample on my blog: http://nik.me/using-html-forms-applescripts
Here’s the generic code to use in your scripts:
(* Nik's generic URI handling scriptlet
By Nik at http://nik.me
This script snippet has a script object that handles any URI/URL using the "open location" Applescript handler. This allows custom URI schemes to activate this script. It's a great way to fire off AppleScripts from web browsers that don't have script support or from shell scripts. If it can make a URL, it can run the applet.
This script takes a URL in the format of "theURI://theMethod?Arg1&Arg2&...ArgN". It handles the parsing of the individual URL arguments and the method, and generates an object containing the results as easy-to-access properties.
The "open location" handler is deliberately simplified to just generate the URL object -- nothing more. From there you can refer to the object to get specific properties, e.g. "get description of myURLObject's args" to get the "description" argument out of the original URL.
There is no built-in validation, so you'll have to manage that after parsing the initial URL.
For this script to work, you must save it as an Application Bundle, and edit the enclosed plist as follows (copied from Apple's website):
-----------
When you save the script as an application bundle, it will contain the standard Mac OS X bundle elements including an XML property list file defining important aspects of the script application.
To access the Info.plist property list, click on the script application with the Control key held down to access the Finder Contextual Menu. Choose Show Package Contents from this menu to open the script application bundle in a new window. Open the Contents folder within the new window to reveal the Info.plist file. Open this file in a text or property list editor and add the following XML keys and values to the property list.
To identify the Application, add the following lines to the property list, replacing the net.mysite.appname text shown here with a unique bundle identifier for your application:
<key>CFBundleIdentifier</key>
<string>net.mysite.appname</string>
To identify the URL handler that triggers the applet, add the following item to the property list, replacing the App Name and theURI text with title of your URL protocol and the URL scheme of your protocol:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>App Name</string>
<key>CFBundleURLSchemes</key>
<array>
<string>theURI</string>
</array>
</dict>
</array>
-----------
You can find documentation on how this works on Apple's website:
http://www.apple.com/applescript/linktrigger/index.html
--------
This script is free to share with anybody you like for any purpose. I'd appreciate it if you'd attribute it back to me (Nik) if you can.
*)
-- Test it here
on run
open location "x-custom-uri://check+this+out?arg1=I%20love%20AppleScript&arg2=Get+more+great+scripts+at+http%3A%2F%2Fnik.me"
end run
on open location sURL
set myURLObject to newURI(sURL)
display alert "The following values were passed: " & return & "SCHEME: " & scheme of myURLObject & return & "LOCATION: " & location of myURLObject & return & "ARG1: " & arg1 of args of myURLObject & return & "ARG2: " & arg2 of args of myURLObject
end open location
(* newURI(): URI Object Initialization Script
This returns a URL script object, containing properties for the URL scheme, location, and arguments, as passed through an HTML-encoded URL. *)
on newURI(u)
script uriObject
property rawURL : missing value
property scheme : missing value
property location : missing value
property args : {}
(* initialize()
This handler initializes the URL object, breaking it out into its constituent parts, and assigns them to the various script object properties. It will also replace and overwrite any existing URL properties on the script object *)
on initialize(aURL)
try
set u to aURL
-- Break out the URL into its various components
set theSplitURL to splitURL(aURL)
log result
--Get the URI-Scheme from the URI
set scheme to item 1 of theSplitURL
-- Get the location from the URI
set location to (decode_text(item 2 of theSplitURL))
-- parse arguments
if item 2 of theSplitURL is not missing value then
set args to argsToRecord(item 3 of theSplitURL)
end if
-- All went well, let's reset our text item delimiters and send back the arguments
set AppleScript's text item delimiters to ""
return {scheme:scheme, location:location, args:args}
on error errMsg number errNum
display alert errMsg & " (" & errNum & ")"
error number -128
end try
end initialize
(* Convert a URL into a record set *)
on splitURL(theURL)
set text item delimiters to ":"
set theURI to text item 1 of theURL
set text item delimiters to ""
set uriN to (count of characters of theURI) + 1 -- account for the ":"
-- Get rid of the url protocol string
set pN to offset of (theURI & "://") in theURL -- is it a mailto:// style?
if pN > 0 then -- a URI:// url
set theURL to text (uriN + 3) through (count of characters of theURL) of theURL
else -- or just a URI: url
set theURL to text (uriN + 1) through (count of characters of theURL) of theURL
end if
-- See if there's any arguments being passed, pass 'em back if there are
set aN to offset of "?" in theURL
if aN = 1 then -- no base url, just arguments
return {missing value, (text (aN + 1) through (count of characters of theURL) of theURL)}
else if aN > 1 then
return {theURI, (text 1 through (aN - 1) of theURL), (text (aN + 1) through (count of characters of theURL) of theURL)}
else
return {theURI, theURL, theArgs}
end if
end splitURL
(* Splits ?key=value&key2=value2 type arguments from the URI and turns them into a {key:value,key2:value2} record set *)
on argsToRecord(argString)
set rStringArray to {}
set text item delimiters to "&"
set splitArgs to text items of argString
set text item delimiters to "="
repeat with a in splitArgs
set ax to text items of a
set axKey to item 1 of ax
set axValue to my decode_text(item 2 of ax)
set rStringArray to rStringArray & {axKey & ":\"" & axValue & "\""}
end repeat
set text item delimiters to ","
run script ("return {" & rStringArray as string) & "}"
return result
end argsToRecord
(* Simple HTML decode routine *)
on decode_text(encodedstring)
do shell script "echo " & quoted form of ¬
encodedstring & " | /usr/bin/ruby -r cgi -e \"print CGI.unescape(STDIN.read).gsub('+',' ')\""
end decode_text
end script
tell uriObject to initialize(u)
return uriObject
end newURI