Transferring data between application and document scripts

I know that in straight Applescript you can load a script and then proceed to call its handlers with

scriptname's handlername(arguments)

Is it possible to do the same thing (or something similar) in AS Studio? Can I call a handler in the application script from the document script? Or vice-versa?

My problem is that I have a document-based application that has a main window that is used to set parameters that will be used to create the document. So when the user clicks a button, the data in the main window needs to be “ported” to the new document. At that point, I need to either a) be able to create a clone of the data that will be kept in a property in the document script or b) be able to “fetch” that data from the application script “on the fly.”

Either solution is acceptable, as long as it works.

Hi Kevin,

yes it works. I think there’s no difference to plain Applescript …

the call in the document.scpt (.applescript):


	set scriptPath to (POSIX path of (path to me) & "/Contents/Resources/Scripts/main.scpt")
	set mainScript to (load script (scriptPath as POSIX file))
	mainScript's test("hello world")

and the handler in the main.scpt (.applescript)


on test(thetest)
	log thetest
end test

D.

Thanks Dominik. I’m curious, when (lets say) the document script loads the application script (or vice versa) if the current data that is in that script is available to it. Or would it be a “copy” of the script that isn’t current?

After some sleep (always good for these kinds of questions) I realized I could probably access the properties of the other script with

tell application "MyApp"
    set aVariable to property myProperty
end tell

I’ll try some tests later today and post the results…

Kevin,

forgive me … i was too curious to wait for your test - here’s the result (as expected):


property theVar : "copy"

on awake from nib theObject
	set theVar to "original"
end awake from nib

on test(thetest)
	log theVar
end test

result (when called from outside): “copy”

But it is also possible to access the ‘original’:

I tried this call from the document script:

	set mainScript to first script of me
	mainScript's test("hello world")

result of the handler above when called this way: “original”

D.

Dominik,
Please, please don’t apologize for running that test! That’s an approach I wouldn’t have thought of. I strongly suggest you post that solution in Code Exchange for other folks, as I know that lots of people have been struggling with how to get data back and forth between the app and doc scripts in AS Studio.

AWESOME! :lol:

of course I will … but before doing so - maybe you can help my solving this problem (regarding the same subject):

the method mentioned above works perfectly if there is only one script - but I haven’t found a solution to access a handler of a second/third … script :-(.

Following the same logic I tried:

set secondScript to my second script -- (or 'script 2 of me' etc.)
secondScript's theHandler("test")

but nada … this doesn’t work

Then I tried:

repeat with theScript in (scripts of me)
		try
			theScript's theHandler("test")
			log " success"
		on error errmsg
			log errmsg
		end try
	end repeat

but again - nothing happens at all.
Why? It seems ‘(scripts of me)’ or ‘(get scripts of me)’ or ‘(get my scripts)’ results an empty list. Why? Isn’t it strange that ‘first script of me’ is possible while ‘me’ has no (countable) scripts at all?

Do you have any ideas?

Dominik

Edit: I found that ‘first’ in the call can be omitted - for example like so:

(get my script)'s theHandler("The argument")

Dominik,

The only thing I can figure out is that this works for the application script because by the time you call for “first script” or “my script” the main script has already been activated.

I suspect it is because the main script is tied to the mainmenu.nib. I imagine that for any other scripts to be called they would have to be tied to an interface element that needs servicing. Try connecting something, let’s say the main window’s “will open” or “awake from nib” to a script other than the main script or the document script, and run your experiment again.

Keep in mind, the document script doesn’t get launched or used until/unless a new document is created. I’ve always thought it was weird that Apple broke the document handlers into another script instead of the main script. In my view, there should probably be only one script for the whole app.

I wouldn’t run off to post anything yet. The reason people have been struggling with this is that applescript does not dynamically interact with other script files, it merely loads them once and uses that representation when subsequent calls to the handlers and properties are made to it. The only thing that this is useful for is loading handlers and routines that you may want to use in multiple scripts. It does not, and is not intended to load data… especially not dynamic data… between different scripts. Using this to load settings or preferences that change during the use of the application is not possible in my understanding of how scripts interact with one another in ASStudio projects.

I’m not surprised that none of these work for you. There is no “script” class in the application dictionary, so referencing one should not work. “first script of me” works for you? Because there’s no such thing as a script (from the application object’s point of view), I wonder what you’re referencing with this statement, and why it’s even working for you.:confused: The techniques that seem to work for you appear to only work within individual scripts for me, not as valid references from other scripts. When I put “first script of me” inside of the document.scpt, I get an error that “mainScript is not defined”. I have yet to find a way to put any code in document.scpt and get the current value of a property in ‘myApp.scpt’. Yes, you can get the default value set in the head of the script file (i.e.: property someValue : “default”), but that’s all. If you change the value of “someValue” programatically in the main script, there’s no way to retrieve the NEW value… you can only get it’s default value that’s hard-coded into the initial declaration of the property. I’d like to see all of your code, as I’m not convinced that you’re actually passing variables between scripts. Perhaps I’ve misunderstood your post.

There are ways to get data between scripts, but loading scripts and referencing them in the way you suggest is not that way. You could write to a temporary file in the bundle, with a text file or a plist. I recommend using the user defaults. As far as I know there’s no “trick” to communicating dynamically between instances of scripts in ASStudio projects. Please, post some working code or a project to prove me wrong, as you’d be solving a long-standing dilemma for ASS developers.

j

Like it or not, logical or not, it does work. I’ve always believed that there had to be SOME way for documents and the parent application to communicate back and forth. Otherwise, you have no reason to even have a document script, if it can’t get data from the app. You’d just put the document handlers in the main script, right?

I’ve set my document window to process the opened event and this is my handler for it:

on opened theObject
	(*create invoice in the new window.*)
	set mainScript to first script of me
	set myData to mainScript's getDocData(title of theObject)
	if myData is not false then
		set formattedDoc to formatData(myData)
	else
		set formattedDoc to "No data"
	end if
	set theView to view "invoiceView" of theObject
	call method "loadHTMLString:baseURL:" of (call method "mainFrame" of theView) with parameter formattedDoc
end opened

The getDocData handler in the main script finds the appropriate record in a list of records (based on the window title) and returns it to the calling script:

on getDocData(docName)
	repeat with myDoc in docData
		if docName = docWinName of myDoc then return myDoc
	end repeat
	return false
end getDocData

The formatData handler just does replacements of character strings with the live data for the invoice, so I won’t list it here, but suffice to say I am getting live data in the invoice.

Personally, I figured there had to be a way to do this without junking up the user defaults. That’s not what they’re for, but I have read several posts where folks are using it because there wasn’t a better way that we knew of.

Comments? Ideas?

No, actually I don’t like it. Because it’s not working for me. The line…

set myData to mainScript’s getDocData(title of theObject)

… generates an error (“The variable mainScript is not defined.”) for me no matter how I word it or what I do to try to make it work. Perhaps you’re doing something on your end that I’m not doing to make this work, but I’m not seeing the behaviour that you are. I’m curious as to what the difference is between your implementation and mine, because I’m no idiot and I can’t get it to work.

EDIT…

Again, the reason everyone’s been doing it some other way, is because until you two discovered it, it wasn’t possible. :stuck_out_tongue: It’s not that I doubt you… OK I do actually, because I can’t replicate it… but there’s never been support for easy communication between scripts. Part of the problem, too, is that applescript studio was never meant to do such things. It was meant to hack small interfaces together, not to develop huge commercial applications. It’s evolved into a different beast, but ultimately most people resorted to “hacks” (as I’d assume you’d call using the user defaults in this case), or by writing it in obj-c.

j

OK, I did figure it out.

The reason it wasn’t working for me, is that you guys must have the “should open untitled” event set to return ‘false’. What happens is that when a document-based app launches, it tries to see if there are any windows besides the document window to open. If there are, it opens them first and does not open an untitled document window. If not, it opens an untitled window. When this happens, it instantiates a new version of the doc script to handle the data for each document that’s opened. By default, doc-based apps WANT to open docs, that’s why it’s necessary to manually disable them if desired. So, if an untitled doc is available to open, then the application: opens the window, which instantiates a new document, which in turn creates a new script object. THIS script then becomes “first script”, not the default ‘myApp.scpt’ which you would expect. And, since there is no formal “script” object of the application, there’s no way to figure this out (other than trial and error) by doing something like “set allScripts to name of (scripts of me)”.

I’m still unclear as to how and why “first script of me” even works, and I’m sure that this will come as a surprise to many people. Perhaps there is some yet unknown method of referencing scripts awaiting our discovery. As it appears now, it is possible to reference just the one first script. Since there’s no way to reference other scripts, people using this technique will have to make sure to set up their interfaces carefully so to be certain that their main script is always instantiated first.

If you do get to posting this somewhere (which IS a good idea, now that we’ve resolved this conflict), make sure to explain that you need to set the “should open untitled” flag to false. If you then want to open a new document, you can do it manually with “make new document” in the launched handler of the app. That way, the document script won’t initialize first.

j

I don’t, unless that’s the default. My document nib is very plain jane, just a window with a web view in it. The window has the opened event checked, but that’s all.

Glad you see that we’re not insane, though. :wink:

The main script should ALWAYS be the first script, as long as it has the handlers for the mainmenu.nib objects. Even if a blank, untitled window opens on first launching the app, the application script should be activated first, unless you’re not using the menus at ALL from the main script.

Kevin & Jobu,

I think I have found the solution:

It seems, the object who ‘owns’ a script is the one that is connected to the ‘awake from nib’ handler (or simply the object that has accessd - and loaded - this script for the first time? I believe this is most likely and would make sense …)

In my example (a modified version of Apple’s ‘Plain Text Editor’) I have added two scripts. The first is connected with the File’s Owner’s awake from nib and it has a handler ‘test()’ - the other is connected with the awake from nib of a window ‘main’ and has a handler ‘test2’. Now I can access handlers of both scripts:

	(get my script)'s test("hello world")
	(get window "main"'s script)'s test2("hello again")

I’ll do some more tests tomorrow (It’s getting too late here …)

D.

Good morning,

I’ve made two more successful tests:

  • added a third script and connected it only to an ‘on clicked’ handler then called it as the button’s script:
	tell button "button" of window "main"
		(get it's script)'s test3("hello button")
	end tell

It is possible to call this handler even before having clicked the button.

  • added a handler in the document script and called it from a button in the main window/script:
on docTest(test)
	display dialog "called document's handler: " & test
end docTest
on clicked theObject
	try
		set theDoc to my first document
		(get theDoc's script)'s docTest("it works!")
	on error errmsg
		log "error " & errmsg
	end try
end clicked

Bingo :slight_smile:

If you have some time … would you please try to confirm these results?

Btw: It seems, ‘script’ is a documented property (see: ‘item’). :wink:

Dominik.

Stupid me … that’s probably the reason why the same object cannot be connected to multiple scripts …
My theory now is, that a script can be accessed by each object that is connected with at least one of it’s handlers …