Applescript Editor's Cocoa-Applescript Applet.app or Xcode template

I’m trying to work out the difference between the Applescript Editor’s Cocoa-Applescript Applet.app template, and Xcode’s ApplescriptObjC template, and would appreciate some clarification.

For example I created an NSWindow with an NSButton in code within the Applescript Editors template, and it displays on the screen as it should, and also created the exact same thing in an Xcode project with the identical code which also displays as it should.
The difference comes when I try to set the target action methods of the NSButton, with the setTarget: and setAction: NSControl methods to the main target script and target handler, in the Applescript Editors template the handler never gets called, and in the Xcode template it does fire the handler.
I’ve not used Interface Builder bindings for the Xcode project, only linking the NSButton in the xib file to a property, and also making the same arrangment in the AS Editor’s project.
I hav’nt shown any code here as the code is the same in both projects, so I’m just trying to work out why there is a difference when it comes to the target action side of the two projects.

Any insights would be much appreciated.

Regards Mark

Applescript Editor’s Cocoa-Applescript Applet.app template is just a finished Xcode project. You can see (and edit) its application delegate class, which just loads your script. The app delegate then calls the run handler of your script. That doesn’t mean your script’s handlers are directly available as part of the app delegate – like in any loaded script object, they have to be targeted via the script object.

I suspect you’re going to have to edit the app delegate script directly, to provide the action handlers there.

But if you’re comfortable enough in Xcode, I’m not sure why you’d bother…

OK thanks Shane, if I understand you correctly, your saying that the code I’m putting into the main.scpt of the AS Editors template, should be put into the App delegate script instead, in the same way as I’m putting the identical code into the Xcode projects App Delegate, I’ll give that a try.

The idea started out as a script library for creating custom dialog boxes and windows, to be called from AS Editor scripts, with the idea of adding additional UI elements from the basic AS dialog boxes. rather than a specific application created with Xcode.

The code exmaple I tested is listed below, so to make it clearer what I was trying to achieve.

AS Editor Cocoa-Applescript Applet code.


use AppleScript version "2.3"
use scripting additions
use framework "Foundation"
use framework "AppKit"

property parent : class "NSObject"
property myWindow : missing value
property myButton : missing value

set myWindow to current application's NSWindow's alloc()'s initWithContentRect:(current application's NSMakeRect(0.0, 0.0, 360.0, 180.0)) ¬
	styleMask:(15 as integer) ¬
	backing:(current application's NSBackingStoreBuffered) ¬
	defer:false
myWindow's setOneShot:true
myWindow's setTitle:("My New Window" as text)
myWindow's |center|()
myWindow's makeKeyAndOrderFront:me

delay (1.0)

set myButton to current application's NSButton's alloc()'s initWithFrame:(current application's NSMakeRect(250.0, 18.0, 90.0, 24.0))
myButton's setButtonType:(0 as integer)
myButton's setBezelStyle:(1 as integer)
myButton's setIgnoresMultiClick:true
myButton's setFont:(current application's NSFont's systemFontOfSize:(13.0 as real))
myButton's setKeyEquivalent:(current application's NSString's stringWithFormat:("\r" as text))
myButton's setTitle:("OK" as text)
myButton's setTarget:me
myButton's setAction:("myHandler:" as text)

tell myWindow's contentView() to addSubview:myButton

on myHandler:sender
	tell myWindow to setTitle:("myHandler: called" as text)
end myHandler:

The myHandler causes the Applet to crash.

The Xcode project code, with identical code and no window xib or bindings.


script AppDelegate
    property parent : class "NSObject"
    property myWindow : missing value
    property myButton : missing value
    
	on applicationWillFinishLaunching:aNotification
        
        set myWindow to current application's NSWindow's alloc()'s initWithContentRect:(current application's NSMakeRect(0.0, 0.0, 360.0, 180.0)) ¬
            styleMask:(15 as integer) ¬
            backing:(current application's NSBackingStoreBuffered) ¬
            defer:false
        myWindow's setOneShot:true
        myWindow's setTitle:("My New Window" as text)
        myWindow's |center|()
        myWindow's makeKeyAndOrderFront:me
        
        delay (1.0)
        
        set myButton to current application's NSButton's alloc()'s initWithFrame:(current application's NSMakeRect(250.0, 18.0, 90.0, 24.0))
        myButton's setButtonType:(0 as integer)
        myButton's setBezelStyle:(1 as integer)
        myButton's setIgnoresMultiClick:true
        myButton's setFont:(current application's NSFont's systemFontOfSize:(13.0 as real))
        myButton's setKeyEquivalent:(current application's NSString's stringWithFormat:("\r" as text))
        myButton's setTitle:("OK" as text)
        myButton's setTarget:me
        myButton's setAction:("myHandler:" as text)
        
        tell myWindow's contentView() to addSubview:myButton
        
	end applicationWillFinishLaunching:
    
    on myHandler:sender
        tell myWindow to setTitle:("myHandler: called" as text)
    end myHandler:
    
	on applicationShouldTerminate:sender
		-- Insert code here to do any housekeeping before your application quits 
		return current application's NSTerminateNow
	end applicationShouldTerminate:
	
end script

The myHandler works as expected.

Just could not work out why one worked, and one would not.

Regards Mark

You were correct Shane, when the code is placed in the AS Editors Cocoa Applet App Delegste script it works the same as the Xcode project template, but not when in the main.scpt.
Also you can add just the target action methods to the App delegate for it to work also like this.


myButton's setTarget:(script (path to resource "CocoaAppletAppDelegate.scpt"))
myButton's setAction:("myHandler:" as text) -- Handler now in the App Delegate script

So I can only conclude that the NSControl’s target action mechanism requires an instance of NSApp in order to function, which means the idea of using a Script Library to create UI’s on the fly could be problematic, as I don’t beleive you can create an instance of NSApp in a Script Library, which will then target a calling AS script.

Regards Mark

I was recently trying to do something similar. My goal was to have a pure AS script bundle pass a nib file from its resources and a script object containing the properties to be bound to the UI elements in the nib file to a script library that would take care of loading the nib, set up the bindings and display the UI. I could make it work in the end, but it turns out that this approach is unsupported: ASOC-enabled libraries must keep any references to Objective-C objects contained within the library. That is, using a script library to bind UI elements to the properties of a script object coming from the AS runtime is asking for trouble.

What you can do, it works and, as far as I know, is fully supported, is to have the script library itself define the UI (programmatically or via nibs), and have a pure AS script invoke the library to display the UI and to query the results.

[Edit: sorry, I didn’t notice that you’d seen this thread] This was discussed recently here: http://macscripter.net/viewtopic.php?id=41963 (unfortunately, I started the thread in the AS forum). See, in particular, this post: http://macscripter.net/viewtopic.php?pid=170345#p170345.

I’d change one thing in that script library: I’d set the outlet properties in DialogData to missing values, instead of assigning them a defined value. Although it works, officially outlets are recognised because they are set to missing value.

When a control has no target, it looks for an object that implements its action in the responder chain. So it starts with the first responder, through any view hierarchy, and eventually to the application, and finally its delegate. When your delegate loads a script, its handlers don’t become methods of the delegate, so they aren’t found.

Thanks Shane & druid for the tips, I have created a script library that can create windows and controls, all of which can be manipulated in any number of ways, and all this can be called from a plain AS Editor script or applet, but the responder chain target action mechanism is the stumbling block, and might be the area where the whole idea falls flat.
But I shall experiment a bit further, and see if I can use the libray to create an NSApp instance and delegate, but I’m not sure at this pont if this is possible.

A nice idea to create user interfaces from a traditional Applescript, but I’m starting to see why previous attempts by others have ended up as a scriptable faceless background application, so I’ll post if I find a solution to this problem using a script library.

Regards Mark

You may have more luck if you skip nibs. Have a look at the progress bar example in the sample scripts you can download from the ASObjC Explorer for Mavericks Web page.

It seems to me that the Cocoa-AppleScript Applet template in AS Editor is meant to emulate a pure AS script applet (that is, a pure AS script saved as an application), not a full-fledged ASOC application”for which you should use Xcode. Hence, in main.scpt you are not supposed to put ASOC code, just pure AS code. This may be the reason of the crash (note that main.scpt is loaded with “load script” by the app delegate).

As I said before, things work as long as you keep all references to Objective-C objects within the ASOC runtime. I do not see why the target-action mechanism would not work in that case, no matter whether you create your UI programmatically or using nibs (the code from the post I’ve linked to above “just worksâ„¢”).

If you want to create a script library that provides UI components to AS scripts, that means that your target handlers should be defined inside the library. Note also that values from UI controls must be coerced to AS data types before being passed back to the calling script.

In the past, faceless background applications were used because there was really no alternative. But script libraries provide a much better way to add a UI, in my opinion.

But what would be the point of it then?

No, the whole point is to give you a way of running ASObjC code without resort to Xcode. You can see it in the examples posted here macosxautomation.com/lion/applescript.html

They provide a much better way to use ASObjC generally, IMO. But the Cocoa-AppleScript app has the advantage of producing an app that will run under versions of the OS back to 10.6, so I think they were right not to abandon the facility.

@Shane Ok, you’re right about the Cocoa-AppleScript template. Then, the behaviour of the OP’s script should be considered a bug. But, anyway, since he wants to build a script library, I don’t think he should use that template anyway.

@Mark: To be more explicit, the following minimal variation on your code works perfectly fine as a script library:


use AppleScript version "2.3"
use scripting additions
use framework "Foundation"
use framework "AppKit"

--property parent : class "NSObject"
property myWindow : missing value
property myButton : missing value

on displayWindow()
	set myWindow to current application's NSWindow's alloc()'s initWithContentRect:(current application's NSMakeRect(0.0, 0.0, 360.0, 180.0)) ¬
		styleMask:(15 as integer) ¬
		backing:(current application's NSBackingStoreBuffered) ¬
		defer:false
	myWindow's setOneShot:true
	--	myWindow's setTitle:("My New Window" as text)
	myWindow's |center|()
	myWindow's makeKeyAndOrderFront:me
	
	delay (1.0)
	
	set myButton to current application's NSButton's alloc()'s initWithFrame:(current application's NSMakeRect(250.0, 18.0, 90.0, 24.0))
	myButton's setButtonType:(0 as integer)
	myButton's setBezelStyle:(1 as integer)
	myButton's setIgnoresMultiClick:true
	myButton's setFont:(current application's NSFont's systemFontOfSize:(13.0 as real))
	myButton's setKeyEquivalent:(current application's NSString's stringWithFormat:(return))
	myButton's setTitle:("OK" as text)
	myButton's setTarget:me
	myButton's setAction:("myHandler:" as text)
	
	tell myWindow's contentView() to addSubview:myButton
end displayWindow

on myHandler:sender
	tell myWindow to setTitle:("myHandler: called" as text)
end myHandler:

Save the code as a script bundle in your ~/Script Libraries folder and check the “AppleScript/Objective-C Library” checkbox in the Bundle Contents Drawer. Then, you can use it from any script:


use AppleScript version "2.3"
use scripting additions
use MarkFX : script "MarkFX"
tell MarkFX to displayWindow()

Not necessarily. The problem is that the target is set to me, but Cocoa does not see that me as an instance of a class containing the action handler; the handler is not a handler of the app delegate.