Subdividing large applescripts into smaller Xcode files

I’m converting a lengthy applescript to AppleScriptObjC and would like to subdivide it into smaller applescript files for ease of maintenance. Is this necessary?

The main file would have declarations, awakeFromNib() and some common handlers. A second file would have handler A. A third file would have handler B, etc. If this is possible, how do I set up the handler files to reference the properties and common handlers in the main file?

Thanks for any insight that can be provided.
Terry

I have been wondering the same thing. In ASS i subdivided long codes, and loaded the “secondary” scripts from the “main” one, and although I never found way to return data from the secondary scripts to the main one, it served me well.

Have things changed in ASOC? Could we, for example, call “classes” of ASOC from the main class, like in ObjC you use the import function to call on different sets of foundation and classes?

And can data be returned from one script to another? This would really improve my coding life…

Model: MacBookPro2,2
Browser: Safari 531.9
Operating System: Mac OS X (10.6)

Yes, you can have multiple AppleScript classes – just go to File → New File and select AppleScript class. And yes, you can call handlers across AS classes – you just have to use the ObjC naming conventions, and also coerce the values each time.

Whether it makes sense will probably depend on the size of the project, among other things.

Thanks Shane, you have open many possibilities for making my maintenance life easier. I had a feeling it was possible, but needed an expert’s advice before diving into it. The change from Studio to Objective-C is good.

Yes, I agree, the change to ASOC is good… Lots of people got scared in the beginning, but finally it looks like a major improvement (in the right direction) for us!

Now if only the compiled apps could run under 10.5…

I for one would be most obliged if someone could provide a brief explanation of how to do this (call handlers across AS classes) with ASOC. My projects are getting too large for a single .applescript.

Thanks for all the help so far.

We had a discussion about this on the ASS list

http://lists.apple.com/archives/AppleScript-Studio/2009/Sep/msg00070.html

I altered Shane’s sample App here (hope that’s alright Shane…)

http://rdutoit.home.comcast.net/~rdutoit/pub/ASObjC_sample_with_Class.zip

Look at the AppController script and the WillFinishLaunching handler

on applicationWillFinishLaunching_(aNotification)
		--here we can set TheFruit's value in the other script 
		AppController's setTheFruit_("Banana")
end applicationWillFinishLaunching_

I simply added a blue cube in IB and connected it in the info pane to the AppController Class (applescript) to create an instance.

Craig’s solution from the post:

  1. In AppDelegate add => property AppController : missing value
  2. In IB, control-drag from the instance of SampleAppDelegate to the instance of App Controller and choose “AppController”

Now that you have a pointer to the instance of AppController
you can get the value of theFruit with:
AppController’s theFruit()

Or set theFruit with:
AppController’s setTheFruit_(“Orange”)

Hope that helps. It works and it is great news! I wonder if there is an even easier way without creating the instance etc… like the “import” in OBJ-C

Rob

That will help very nicely! I missed the posting at the list, so thanks for the links.

You can call a handler in another class pretty easily, but there are a couple of things to be aware of. For one, you can’t pass application references. There’s also no need to make an instance of the class; AppleScript handlers are treated as both class and instance methods.

Let’s suppose you have a class called OtherScript and you want to put a handler like this in it:

on doStuff(a, b, c)
return (a + b) / c
end doStuff

First, you have to match the Cocoa naming convention. So you need to rename it something like do_some_stuff_ or even dostuff___. So let’s say you have this:

on do_some_stuff_(a, b, c)
return (a + b) / c
end do_some_stuff_

To call it from your first script, you need to refer to the relevant class, which in turn belongs to the application. So something like:

set x to current application's class "OtherScript"'s do_some_stuff_(5, 7, 2)

Because you’re calling the handler via Cocoa, x is going to be a pointer, and you’re going to have to coerce it to an integer or number before you can do anything with it.

But in fact you won’t get that far, because the same problem also happens when you pass arguments to a handler via Cocoa – so your handler will fail because a, b and c are not recognized as numbers. You need to change your handler to something like:

on do_some_stuff_(a, b, c)
set a to a as real
set b to b as real
set c to c as real
return (a + b) / c
end do_some_stuff_

Now it should work.

All that coercing is going to get tedious, especially if you have lots of parameters or handlers that return a lot of values, but in such cases you can reduce the amount you need to do. The key is to pass a single parameter as a list.

So the call becomes:

set x to current application's class "OtherScript"'s doStuff_({5, 7, 2})

And the handler becomes:

on doStuff_(d)
set {a, b, c} to d as list
return (a + b) / c
end doStuff_
end script

The important point here is that coercing the single argument to a list effectively coerces the values it contains to their respective classes.

Hi Shane,
That is great because I want to pass a long list of options to the other script to process which would be tedious to “uncork” them all one at a time.

What about setting and getting the property value in the other script though? I tried (without the blue cube):

on applicationWillFinishLaunching_(aNotification)
		--here we can set TheFruit's value in the other script without creating an instance to refer too? 
		current application's class "AppController"'s setTheFruit_("Banana")
end applicationWillFinishLaunching_

Do I have to coerce “Banana” to cocoa speak? @“Banana” or something?

Rob

Shane,

Thanks. Your tips are very helpful and much appreciated.

What I was wondering is if it is possible to set another script’s property from the outside. No problem with setting the variables inside a handler.

So if the property theFruit is bound to the radio button.

In the delegate script

 on applicationWillFinishLaunching_(aNotification)
		current application's class "AppController"'s setTheValueNow_({"Banana"})
end applicationWillFinishLaunching_

and in the AppController script

property theFruit : "Peach" -- the selected value of the radio button is bound to this property

on setTheValueNow_(d)
		set {theval} to d as list
		display dialog "" & theval & ""  --this works as we know...
	        setTheFruit_(theval)  -- this doesn't change the radio button property - it stays "Peach"
end setTheValueNow_

I thought I could treat it as an OBJ-C style setter in the AppController class. I tried various permutations with no luck. Even just calling the handler containing:

setTheFruit_(“Banana”)

It doesn’t work though you can run the handler from the interface via an outlet and it does work!

Rob

The problem is that your code is trying to do things to the class, when what you want to do is call handlers and change properties in a particular instance of the class, the one in the .xib file. So rather than addressing ‘current application’s class “AppController”’, you need to make a new property, connect it to the instance of AppController in IB, and then target that property.

Yes - I kind of figured it was a class thing. In my original example I had created the instance and it is easy to access that through the property:

appController’s setTheFruit_(“Banana”)

That works fine when called from another script that is connected to the outlet for the AppController instance. Or something like that.

But then we can call handlers in that class and pass them values via your previous method coercing the values both ways. I don’t quite understand the difference between that and accessing the properties as the class vs. instance concept is taking some time to get used to.

Is the reason I couldn’t access the class itself about data encapsulation? The class is protected from the outside?

But good news that we have ways to communicate between script classes.

thanks,

Rob

Think of an instance as a copy of the class – once it’s made, it’s a distinct item. It’s that instance you are using in the app, and so it’s that instance’s properties you want to act on.

I have a question that, I think, is related to this.

I’m writing a Cocoa application with Objective-C in XCode. The app is supposed to control iTunes and some other applications with Applescript, but I want to keep the actual app in Objective-C for efficiency and convenience reasons.

I began by creating a new Objective-C Application project in XCode (the template was Cocoa Application, I think). I have a regular AppDelegate-class (.c and .h files) where most of my code resides. I would like to create a separate Applescript Class (mycustomclass.applescript) with handlers that I could call from AppDelegate-class with Objective-C commands.

I can’t make that happen.

I have tried the following:

  • In XCode: File → New File → chose to create an applescript class. I put the following in there:
script mylittlething
	property parent : class "NSObject"
	
	on dialogia()
		display dialog "What's the deal?"
	end dialogia	
end script

  • In AppDelegate.m I put the following:

  • (void) etsi: {
    NSLog(@“Log this and that”);
    [mylittlething dialogia]
    }

But, I get an error:
[mylittlething dialogia] undeclared (first use in this function)

What should I do to make that dialogia() handler visible and usable for AppDelegate? I tried to search for an answer from previous forum posts, since this seems like a basic thing, but in vain. So apologies if I’m repeating the question.

I appreciate any help. (First time poster here, so please tell me if I’m in wrong forum or something, I will learn.) And please tell me if you need more information about the problem or project concerned.

K

That’s probably your problem. Start with an AppleScript app, or else modify your project to match – it needs an extra line in main.m, and you need to add the AppleScriptObjC.framework to the project.