NSArray
, NSURL
, NSUserNotification
, and so on. You may not be familiar with those classes, but their names hint at what they do. Because of its importance, you can use its classes without needing to import it in new apps. They are available by default.NSWindow
, NSTextField
, NSTextField
, and NSButton
.NSOpenPanel
that allows you to select a file. We’ll configure the panel to restrict the file selection to .jpg, .png, or .gif.Applications > Utilities
. Script Editor ain’t the best editor I’ve ever used, but it’s necessary for now. It has a bunch of features that we need for building JS OS X apps. I’m not sure what goes on beyond the scenes, but it can compile and run your scripts as apps. It also creates extra stuff that we need like an Info.plist file. My guess is there are ways to make other editors do the same, but I haven’t looked into it yet.File > New
or cmd + n
. The first thing we need to do is save the document as an application. Save with File > Save
or cmd + s
. Don’t confirm the save right away. There are two options that are necessary to make this run as an application.Script > Run Application
or opt + cmd + r
.File
and Edit
menus are in the menu bar. You can see the app is running because its icon is in your dock.File > Quit
or cmd + q
and we’ll find out where that NSLog
went.Applications > Utilities > Console
. Every application can log messages to the console. This console is not much different than Developer Tools in Chrome, Safari, or Firefox. The main difference is you use it for debugging applications instead of websites.opt + cmd + r
.$.foo
or ObjC.foo
. There are a couple of other ways to use $
that I’ll cover later.NSLog
are indispensable tools, you’ll use them non-stop for debugging. For examples of logging things other than strings, have a look at my NSLog example.opt + cmd + r
. Now we’re talking! With a small amount of code we’ve built an app that launches a window with a title that we can move, minimize, and close.styleMask
? You use style masks to configure windows. Each style option says what it adds; a title, a close button, a minimize button. These options are constants. Use a pipe “|” to separate options. That pipe is a C bitwise OR
operator. I’m not gonna pretend to know what that means. I just know it’s needed to combine style options for a mask and that’s good enough for me.NSResizableWindowMask
is one you’ll use. Try adding it to the style mask to see what it does.$.NSWindow.alloc
calls the alloc
method of NSWindow
. Notice there are no parenthesis “()” after alloc. In JavaScript that’s how we access properties, not how we call a methods. What’s with that? In JS for OS X parenthesis are only allowed when calling a method if you pass arguments to it. If you use parenthesis with no arguments, you will get a runtime error. Check Console for errors when things don’t do what you think they should.NSWindow
docs for that method and you’ll notice it looks a bit different.[NSWindow alloc]
calls the alloc
method of NSWindow
. For JS, convert those to dot-notation and parenthesis if necessary; NSWindow.alloc
.NSTextField
and NSButton
to make that happen. Update your script with the following code and then run the app.textFieldLabel
and textField
are similar. They’re both instances of NSTextField
. We make them in a similar way as we made the window. When you see initWithFrame
and NSMakeRect
, it’s a good sign that the script is creating a UI element. NSMakeRect
does what it says. It makes a rectangle with the given position and dimensions; (x, y, width, height)
. That creates what’s called a struct in Objective-C. In JavaScript we’d refer to it as an object or hash or maybe dict. It’s a thing with keys and values.label
element. For that we make our own by disabling editing and background styling.NSButton
. Like the text fields, creating it requires drawing a rectangle. There are two properties that stand out; bezelStyle
and buttonType
. The values for both are constants. These properties control how the button will render and the style it will have. Check out the NSButton
docs to see everything you can do with it. I also have an example app that shows the different button types and styles in action.addSubview
. When I first tried this I did window.addSubview(theView)
. That works for other standard views that you create with NSView
, but not for instances of NSWindow
. I’m not sure why that is, but for windows you need to add sub views to the contentView
. The docs describe it as; “The highest accessible NSView object in the window’s view hierarchy”. Works for me.target
and action
. The target
is the object we want to send the action
to. If this doesn’t make sense now, just keep going and it’ll get more clear when you see the code. Update the button setup part of the script with the following properties.appDelegate
and btnClickHandler
don’t exist yet. We need to make them. In the next addition to the script, order matters. I put comments in the following code to show where you should add the new stuff.ObjC.registerSubclass
all about? Subclassing is a way to create a new class that inherits from another Objective-C class. Side note; the likelihood I’m using incorrect terminology here is through the roof. Bear with me. registerSubclass
takes a single argument; a JS object with the members of the new object. Members can be; name
, superclass
, protocols
, properties
, and methods
. I’m not 100% sure that’s an exhaustive list, but that’s what’s in the release notes.superclass
we inherited from NSObject
. That’s the root class of most Objective-C classes. Setting a name
let’s us reference the object later through the bridge with $
or ObjC
.$.AppDelegate.alloc.init;
creates an instance of our AppDelegate
class. Again, notice we don’t use parenthesis with the alloc
or init
methods since we’re not passing arguments.types
and implementation
members. I haven’t found official docs for what the types
array should contain. Through trial and error I found it goes like this:btnClickHandler
won’t return anything so we set the return type to void
. It takes one parameter, the sender object. In this case it will be the NSButton
we named btn
. We use the “id” type because represents any object.implementation
is a normal function. Inside of it you write JavaScript. You have the same access to the $
bridge as you do outside of an object. You also have access to variables you create outside the function.protocols
array your script will just stop with no errors. I wrote an example and explanation that you’ll want to have a look at if you’re doing that type of thing.btnClickHandler
implementation function with following code:NSOpenPanel
. If you’ve ever selected a file to open, or a location to save a file you’ve seen panels in action.allowedFileTypes
lets us specify the allowed types for the panel. It expects an NSArray
. We create a JS array with allowedTypes
, but we need to convert that to an NSArray
. We make that conversion with $(allowedTypes)
. This is another use of the bridge. You use it in this way to bridge a JS value to Objective-C. To bridge an Objective-C value to JavaScript you’d use $(ObjCThing).js
.panel.runModal
. That pauses code execution. When you click Cancel or Open, the panel will return a value. If you clicked Open, the contstant value of $.NSOKButton
is returned.panel.URLs
is important. In JS, we access the first value of an array with array[0]
. Because URLs
is an NSArray
we can’t use bracket notation. Instead we use the objectAtIndex
method. They both give the same results.NSImage
. Since it’s common to create an image from a file URL, there’s a handy method doing it:NSImageView
using the same process we did for creating other UI elements. imgView
handles displaying the image.setFrameDisplay
to change the size of the window.opt + cmd + d
. You run the actual app by double clicking its icon like any other application./Contents/Resources/applet.icns
. To access the app resources, right click on the app icon and choose “Show Package Contents”.