creating a persistent property in applescript studio??

property values dont seem to persist across runs in applescript studio applications the way they do in applescript editor applications. How do I get persistence.

here is a sample dropplet:


property someValue : "nothing set yet"

on open names
      -- let user enter a value
	display dialog "give me the new default value" default answer someValue
	set t to the text returned of result
       -- prove we got the new test value
	display dialog t
       -- set the persist property
	set someValue to t
	quit
end open

When I create this in applescript editor and drop something on this, I can enter a new value for someValue in the dialog. When I drop something a second time, the value of someValue is whatever I set it to the previous time.

When I create this same script in applescript studio as a droplet the value of someValue does not persist across drop invocations. it always comes up as “nothing set yet”.

So how does one create a peristent property in applescript studio?

Okay I finally figured this out. Sheesh its barely documented. Searching the studio refernence for persistence comes up with NADA. What kind of crazy documentation is this!

In any case the answer is that there is no conceivable use for the property declaration in Applescript studio. Which basically breaks all comaptibility with applescript.

if you want a persistent variable you have to stand on your head. read the section on user-defaults class.

-- testproperty.applescript
-- testproperty

--  Created by Charlie Strauss on 9/4/05.
--  Copyright 2005 __MyCompanyName__. All rights reserved.

property someValue : "this is the string"


on idle
	(* Add any idle time processing here. *)
end idle



on open names
	-- the magic incantation to retreive a persistent property value  or instantiate a new one with a default value
	make new default entry at end of default entries of user defaults with properties {name:"defaultName", contents:"Testing"}
	
       -- now actually retrieve that value.
	set someValue to contents of default entry "defaultName" of user defaults
	

	display dialog someValue
	display dialog "some text" default answer someValue
	set t to the text returned of result
	display dialog t
	set someValue to t

       -- now set that value so it will persist.
	set contents of default entry "defaultName" of user defaults to someValue
	
	-- Remove the following line if you want the application to stay open.
	quit
end open


How do I erase the defaults and start over? in applescript editor simply editing a file reset the properties, but this is not so in the applescript defaults.

Just a guess, but does this work?:

delete every default entry of user defaults

I believe you can also achieve property persistance by creating a libray of script routines.

The you load the script with its properties via.

set myLib to load script …

you then access the properties via.

set myProperty of myLib to “tester”

the property persists if you then.

store script myLib…

Please see the use of load script and store.

All the best

tellboy

The user-defaults class is very useful, especially once you learn about Bindings.

If you would describe what it is you’re doing in AS Studio, perhaps we can offer you some alternatives to properties.

What I’m trying to do is the following.

I have a dropplet that processes files dropped on it. But if you just double click to open it rather than dropping, it pops up a preferences panel. THe preferences panel has controls inclucing a textbox, a radio button, and come check boxes. I need the properties set in the preferences panel to be persisitent.

Now when you mention bindings I get intrigued because a lot of what I do is sort a lot of copies of information. THat is when the application runs it creates default values in the user-defaults, reads back the values stored in user-default into property variables, copy these properties to the text fields,radios,checkboxes of the preferences window. When the window closes the reverese happens: the window’s field values are copied back into the properties, the properties are then stored into user-defaults, there’s a cocoa call to syncronize object to assure the user-defualts get written out to disk and the application quits.

I’m guessing there might be a way to shortcut copying some of this if the window fields could be bound to the user-defaults directly. I’d still have to read them during the dropplet phase I guess.

here’s a sample of what I’m doing right now:

in the section labled awake from nib you can see the copies from the user defaults to the property variables and then copies from the property variables into the fields and checkboxes of the window.

in the section called doConfigure you see the reverse of copyting from the window fields to the property variabels and then to the user-defaults.

the load_all_porperties and save_all_properties has to hard code the names of the properties. So if you add a property you have to add it to the list of properties to be saved as well. (YUCK!)

property scriptText : "this is the text field contents"
property hideOutput : false
property debugMode : true



on load_all_properties()
	
	set property_list to {{name:"scriptText", theValue:a reference to the scriptText}, {name:"hideOutput", theValue:a reference to the hideOutput}, {name:"debugMode", theValue:a reference to the debugMode}}
	
	repeat with theSet in property_list
		set y to theValue of theSet
		set r to loadDefault(the name of theSet, theValue of theSet)
		set the contents of y to r
	end repeat
	
	
end load_all_properties

on save_all_properties()
	
	set property_list to {{name:"scriptText", theValue:scriptText}, {name:"hideOutput", theValue:hideOutput}, {name:"debugMode", theValue:debugMode}}
	repeat with theSet in property_list
		
		persist(the name of theSet, theValue of theSet)
	end repeat
	-- assure write to disk
	call method "synchronize" of object user defaults
	
end save_all_properties

on loadDefault(theKeyName, theValue)
	make new default entry at end of default entries of user defaults with properties {name:theKeyName, contents:theValue}
	return the contents of default entry theKeyName of user defaults
end loadDefault

on persist(theKeyName, theValue)
	
	set contents of default entry theKeyName of user defaults to theValue
	set testit to the contents of default entry theKeyName of user defaults
	
end persist

-- This droplet processes files dropped onto the applet
on open these_items
	-- do stuff here  (we load properties during the nib awake
end open


on clicked theObject
	
	set theName to the name of theObject
	if theName is "doConfigure" then
		doConfigure(theObject)
	else if theName is "doCancel" then
		quit
	end if
	
	
end clicked

on doConfigure(theObject)
	-- laboriously copy the window fields to out to property variables
	
	tell the window "configureWindow"
		set scriptText to contents of the text view "tvScriptText" of scroll view "svScriptText"
		set debugMode to the content of the button "debugMode" as boolean
		set hideOutput to the content of cell "hideOutput" of the matrix "scriptFinished" as boolean
		
	end tell
	
	-- now copy these property variables to the user-defaults
	save_all_properties()
	
end doConfigure


on awake from nib theObject
	
	if theObject is the window "configureWindow" then
		-- copy all the user-defaults into the property variables
		load_all_properties()
		
		-- then copy of these into the text fields of the windows
		tell window "configureWindow"
			set the contents of the text view "tvScriptText" of scroll view "svScriptText" to scriptText
			set the content of the button "debugMode" to debugMode
			-- set both radio buttons in the matrix
			set the content of cell "hideOutput" of the matrix "scriptFinished" to hideOutput
			set the content of cell "showOutput" of the matrix "scriptFinished" to not hideOutput
			
			show -- window "configureWindow"
		end tell
	end if
	
	
end awake from nib



It sounds like Binding would help you out. Binding are only supported in Mac OS X 10.3 or later. If that’s OK, then I’ll get back to you as soon as I can. In the meantime, you can read this.

Edit: If you could send me a picture of your preference panel (or a ZIP file containing the whole project, if you must), that would help.

for a picture goto http://homepage.mac.com/cems/ and select nib.tiff.

Another thing I’m puzzling over is how one removes previously set user-defaults. When I dump the list of all the user defaults I see lots of old junk in there.

Okay the pointer on bindings is slick! What bindings are doing here is actually writing a automatically file in ~/Library/Preferrence/ for any variable in the NIB wondow you specifiy You never even have to ask for this write to disk. You just get it once you click on bindings. THe second thing these things do is they become global variables in your applescript program.

So in a nutshell they replace traditional applescript properties for persistent variables and at the same time get their values right off the interface without one having to ask.

However It does seem sub optimal that you can’t separate that functionality. For example suppose you wanted to have a variable tied to an inteface builder value but you did not want it to be persistent (and therefore written to disk). Looks like you are stuck with the disk write to get that tie.

anyhow back to implmenting this.
So far I’m having three different sorts of problems with this binding.

  1. for some reason I dont understand yet, not all of my bound values are acting peristent. I suspect I must have to do some sort of NSObject synchonization call to force that. But have not figured out the inconsistency.

  2. all copies of an application will use the exact same preferences file!!! this means that if the user makes a new copy of the application, sets the preferences in that copy to some value, then this globally changes the preference in all other copies fo the application. THis is a much different behaviour than persistent properties in regular (non-ASS) applescript. There different copies of apps had separate preference.

THe reason this is undesirable is that for a Dropplet applescript one is often configuring multiple instances of the application to do different things. e.g. put all the files dropped on this dropplet in a particualr folder, and all the files droped on a different dropplet in another folder. One might do this by having the same applescript dropplet be user configurable about the destinastion folder. Can’t do that if all copies of an application share the same properties.

  1. since this is a preferences panel, I want to offer the user a cancel button in case they dont want to save any changes to the preferences they just made. If the interface fields are bound directly to the preference properties then al changes the user makes are committed to the disk as they are made. I don’t see any way around that when using bindings.

To get around that there are two possible ways to go that I can see. The horrid method is the one I started with. Dont use bindings: instead create defaults using the user-defaults class then laboriously copy all of those into the form fields when the panel awakens, then when the window closes laboriously copy all those form fields back out into the user-defaults.

A better method that requires less knowledge about the view one is using to present the text fields is to use bindings as above. But when the window awakes, store a temproary copy of all the bound variables. If the window closes with a “cancel”, you copy back the stored values, otherwise do nothing. THis still requires some laborious copies to specific bound variables but does not require any knowledge of the interface, so it’s much better design.

However implementing this in a nice way is driving me batty.
What I’m trying to do to keep this clean, is rather than hard-code every single copy-out then have a matching hard coded copy-in, is I want to have a list of all the bound variables and then process that list. THis would be a re-usable general purpose “undo” functionality for any bound interface you built. Just supply the list of variables to allow the undo on.

Now to do that one has to have a list of reference to the variable, not their contents. And there is the problem I’m having. I get errors when I try to take reference to bound variables. The message refers to some problem with taking a reference to an NSDictionary type (which I assume is the bound variable’s underlying ccocoa object class)

Here is a snippet of the code where I try to do this:


global orig_property_list   -- I'm going to store the cached values in here
global property_list          -- global variable to simplfy passing this around

-- my bound variables are called "scriptText" and "hideOutput"

on will finish launching theObject
	-- here is key step, a list of names and references to my bound variables
      -- it's this list that I will say which bound variables do I want to keep copies of

	set property_list to {{theName:"scriptText", theValue:a reference to the scriptText},  {theName:"hideOutput", theValue:hideOutput} }
	
	stashDefaultProperties()
end will finish launching


on stashDefaultProperties()  -- make copies of the current values of all the bound variables in the property_list

	set orig_property_list to {}  -- start with empty set and accumulate into it
	repeat with theSet in property_list		
		set thisName to the theName of theSet
		set theOrigValue to theValue of theSet  -- get the bound variable's value
		set the end of orig_property_list to {theName:thisName, theValue:theOrigValue}
	end repeat
      -- no value is returned but the results have gone into the global variable orig_property_list
end stashDefaultProperties

THe errors I are like the above. if you look at what is in the property list it’s missing the “theValue” entries:


{{theName:"scriptText"},  {theName:"hideOutput"}}

So is there a way to do this? or is one forced to use non-reusable hard coded copies?

oops I made a typo above and failed to mention a key concept:

first the typo:

set property_list to {{theName:"scriptText", theValue:a reference to the scriptText}, {theName:"hideOutput", theValue:a reference to hideOutput} }

Second the key missing concept is that I don’t need the references to the bound variables for the copy-out cache step. If I only needed that, then the way I’m creating the orig_property_list could be done in one step, not a loop.
But I do need them for the copy-in step when I go to put the variable values back in.

the copy-the-originals back into the bound variables step looks like this:


on revert()
	repeat with theSet in property_list
		
		set theName to the theName of theSet
		set theNewValue to (a reference to theValue of theSet)
		set the contents of theNewValue to original_value_by_Name(theName) 

	end repeat
	
end revert

original_value_by_Name(thisName)  -- find the entry with this name in the cached property values
	repeat with theSet in orig_property_list
		if thisName is the theName of theSet then return theValue of theSet
	end repeat
	return "Error! Name not in set"
end original_value_by_Name


Okay some progress.

on item 1: some persistent bound items items aren’t. Never did figure it out but I deleted the interface, started fresh and it worked this time.

on item 2: can’t have multiple copies of an application with diffenent preferences. No resolution. I will repost that as a new thread.

on item 3: well the bottom line is you cannot get refences to the user default class. so you have to do it another way. So I did. that’s solved.