get value of tableview items from array controller

OK, you need to use sourcefiles:{} in each instance.

And you can also skip using theOriginalData if you follow up changes to theData like this:

		if bTableView's numberOfRows() = 0 then
			set sourcefiles of item actualSelectedRow of my theData to {{filename:"somefile", filePath:"pathtofile"}}
			set my theData to my theData -- registers change

In fact, this lets you do away with that whole test, replaced by:

		set end of sourcefiles of item actualSelectedRow of my theData to {filename:"somefile", filePath:"pathtofile"}
		set my theData to my theData

You can also set the selection in table 1 like this:

		set x to current application's class "NSIndexSet"'s indexSetWithIndex_(setSelection)
		aTableView's selectRowIndexes_byExtendingSelection_(x, false)

Hmmm, doing it that way you also need to trap for for when there’s no selection in table 1 and someone hits Add for table 2. So maybe:

		if actualSelectedRow > 0 then
			set end of sourcefiles of item actualSelectedRow of my theData to {filename:"somefile", filePath:"pathtofile"}
			set my theData to my theData
			set x to current application's class "NSIndexSet"'s |indexSetWithIndex_|(setSelection)
			aTableView's selectRowIndexes_byExtendingSelection_(x, false)
		end if

And you need to capitalize the “P” in filePath in your binding of the second column of the second table.

You were so close…

I tried that and it didn’t work…

Brilliant. I knew there must be a way to place that more simply.

I actually tried “set my theData to my theData” to see if it would give everything a kick but no luck. But other things were off.

I looked up NSIndexSet and got scared - I had been doing pretty well with OBJ-C and Cocoa stuff till then.

I never would have seen that! One of the major mistakes I keep making is with punctuation. But you know- my applescript could get pretty sloppy with naming conventions and ASOC is forcing me to clean up my act.

A good lesson here; you can try things that will work along the way but you need to know why they are right to write code well.

I got burned out with bindings and lost my way, and missed some crucial things. You have been a great help Shane - thanks.

And this does show the really good possibilities with ASOC going forward- so much less code! I will put together the Two tables as an example project.

Rob

Hi Shane,

I just found one more thing.

When you delete the last row in table 2, you can no longer add a new row. This one seems trickier since the remove call is built and we have no control over it. I’ll ponder this… perhaps a manual remove handler instead.

Rob

And now - I can’t add a new row to table 2 after editing the name of a row in table 1. That did work before.

Rob

Yes, it looks like I was being too smart with “set end…” – the remove is change a list of lists to a simple list. Change back to this:

			set sourcefiles of item actualSelectedRow of my theData to {{filename:"somefile", filePath:"pathtofile"}} & sourcefiles of item actualSelectedRow of my theData

That does it. It is looking very stable now. Table 1 edits and table 2 removes cleanly…

My only last wish is that sets could be added to the end of the table 1, which is the expected behavior. I switched

set my theData to {{backupsets:"NewSet", sourcefiles:{}}} & my theData

to

set my theData to my theData & {{backupsets:"NewSet", sourcefiles:{}}}

and this seems to work now. You had mentioned that it may cause theData to become a pointer, or something like that?

Thanks, Shane

Rob

It works because of the extra “set my theData to my theData” line.

It’s been a bit of a battle, and you can see why bindings have a reputation for being fiddly. But even if you’d just used them to supply the data, and did the adding and deleting by code, I reckon they still beat fiddling with data sources and table delegates. And some people are happy with just one table :slight_smile:

A few subtleties with the near perfect two_tables_example project:

The one thing I hadn’t tried was sorting the columns. An old friend appeared. In ASS list there were many posts about sorting a table and the datasource not reflecting the new order. The same appears here.

With a several sets created:
Sort table 1 and then select set 1
Add an item to table 2 and the item gets added to the last set, not the set you selected!

This is because our bound variable theData is not sorted as well.

I have looked at all the settings and can not find anything there. In ASS I remember getting the id of a row and then proceeding from there but I don’t see id anywhere.

And unfortunately, setting the new set to the end still doesn’t work- it causes buggy behavior even with

set my theData to my theData

I tried all sorts of trickery but can’t put at the end.

It is not the end of the world if the new rows go to the end and sorting is off for table 1, but users will be asking why… etc.

rob

Yes, the sorting doesn’t go back to theData. The array controller has an arrangedObjects method; perhaps you can call that, add/insert to what it returns, and set theData to the result.

Just for completeness here is the solution to getting the sorted item in a table.

the newFilepath variable is retrieved from an open file dialog and then we get the filename and icon

set newFilename to do shell script ("basename " & quoted form of newFilepath)
		
set theIconImage to NSWorkspace's sharedWorkspace()'s iconForFile_(newFilepath)

Then - instead of accessing theData variable (which is bound to the array controller’s content) we ask the array controller itself for its arranged objects, and as the name suggests, that is the sorted array.

Then we get the objectAtIndex which is the particular selected object (or the record’s item) in the Array. And ask it for it’s valueForKey_(“sourcefiles”) which is the key in the array containing another array which is the record of items displayed in table 2. This can then be added to with a new record for the new row we want to add to table 2.

    if actualSelectedRow > 0 then 
	   set indexnum to FileSet's selectionIndex()
	   set theOldArraysValues to FileSet's arrangedObjects()'s objectAtIndex_(indexnum)
	   set theOldTableValues to theOldArraysValues's valueForKey_("sourcefiles")
	   set theNewTableValues to {{fileicon:theIconImage, filename:newFilename, pathtofile:newFilepath}} &  theOldTableValues
	   theOldArraysValues's setValue_forKey_(theNewTableValues, "sourcefiles")
   end if

All along I was sure this would never work - that I would have to create an array object first to send to the Array Controller’s Array. But, as Shane suggested, it seems we can use a plain old applescript record which we AS folks all know and love.

Now I have two tables connected with bindings using about ten lines of code, and only the code needed to add items to them! This creates a “Source List” style of app where data in the right table is tied to the “source” items in left table.

After years of struggling with two tables in ASS, this is a miracle, to me any ways. And being able to write one line of code to get the file’s icon without needing a separate OBJ-C class file is cool too.

Thanks everyone for helping me to solve this.

I already posted the new sample app but here it is:

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

Rob

Ok, I have my two table sample working great, So now I want to save the table data.

I try saving it to user defaults simply but realized I have set my record to contain image objects

set theValue to NSWorkspace's sharedWorkspace()'s iconForFile_(newFilepath)
set theNewTableValues to {{fileicon:theValue, filename:newFilename, pathtofile:newFilepath, filesize:"---"}} & theOldTableValues

This creates weird corrupt plist files. So I remove the images and it all works.

Then I try adding in the images at run time and removing them at quit. That works but the defaults plist is being written to during runtime and the odd plist files are created as I add rows to the table etc…

So forget User defaults. Can I write these records to file somehow, including the image objects? Remember we are dealing with a record here…

{{backupset:"newBackupset", sourcefiles:{{fileicon:theValue, filename:newFilename, pathtofile:newFilepath, filesize:"---"}}}} 

I have tried to convert it to NSMutableArray with no luck. This is an OBJ-C area that really confuses me.

I also read that you can have a datasource for a table that uses bindings but can’t get that to work so far. I figured I could then use the table datasource methods to set the images as Craig does in booklist example.

So close again but not sure of what direction to take this. Any guidance appreciated.

Rob

I should really start a new thread here but…

It isn’t the image that causes the corrupted plist file. It is the open panel!

my plist file name is normally
com.rdutoit.backuplistc.plist

When the open panel gets called:

set openPanel to current application's class "NSOpenPanel"'s openPanel()
		tell openPanel
			set allowsMultipleSelection of it to true
			set canChooseDirectories of it to true
			set allowsOtherFileTypes of it to true
			runModal()
			set fileUrl to |URL| of it
			if fileUrl is missing value then
				return
			end if
			set newFilepath to |path| of fileUrl as text -- Error occurs without 'as text'
end tell

When I press the “add” button it triggers the above open panel call. At that moment and after accepting a choice in the open panel, endless new defaults plist files are generated with a names like

com.rdutoit.backuplistc.plist.7gSYoM7
com.rdutoit.backuplistc.plist.0XVtcPM
etc.

They are corrupted files!

I suspect something odd is happening with the URL word?

Anyone have a clue here.

I am suing this for defaults write:

-- on Awake from nib
tell NSUserDefaults of current application
	tell its standardUserDefaults()
		set storedDataArray to its arrayForKey_("theArrayRecord")
		set my theData to storedDataArray
		if my theData is in {"", missing value} then set my theData to {{backupset:"newBackupset",sourcefiles:{}}} 
	end tell
end tell

-- on quit
tell NSUserDefaults of current application
	tell its standardUserDefaults()
		its setObject_forKey_( my theData, "theArrayRecord")
	end tell
end tell

Rob

Hi Rob,

Not really sure of why you have a problem but I have used this snippet without any problems:

It may help

All the best

Terry



		tell class "NSOpenPanel" of current application to set myOpenPanel to its openPanel
		
		myOpenPanel's setAllowsMultipleSelection_(false)
		myOpenPanel's setCanChooseFiles_(true)
		myOpenPanel's setCanChooseDirectories_(false)
		
		myOpenPanel's runModal
		
		set tFileArray to myOpenPanel's filenames()
		set tFilepath to tFileArray's objectAtIndex_(0)
		tell MyView to setImageWithPath_(tFilepath)


Hi All,
Back with my two tables experiment. All is well and I am trying to now save the table data. USer defaults weren’t working for reasons I posted earlier so I tried “writeToFile” which seems to be working. Here it reads in the file and then writes out.

on awakeFromNib()
		set theNewArray to NSMutableArray's alloc()'s initWithContentsOfFile_(FILE_PATH)
		if theNewArray is equal to missing value then
			set theNewArray to NSMutableArray's alloc()'s init()
			set theData to {{backupset:"newBackupset", sourcefiles:{}}}
			theNewArray's addObjectsFromArray_(theData)
			set my theData to theNewArray
		else
			--workaround for empty image column. We set the images one by one!
			set my theData to theNewArray
			my addImagesAfterArchiving()
		end if
		set my theData to my theData
	end awakeFromNib

on writeToFile()
		set theNewAnotherArray to NSMutableArray's alloc()'s init()
		theNewAnotherArray's addObjectsFromArray_(my theData)
		if not theNewAnotherArray's writeToFile_atomically_(FILE_PATH, true) then
			set messageText to "There was an error writing to file"
			display dialog messageText buttons {"Ok"} default button 1
		end if
end writeToFile

What confuses me is I am using a record but then it gets turned into an array so it will archive. This seems to work. One little bug though is if you add a new set to table one and try to quit without editing the set name, it throws the “WriteToFile” error and doesn’t retain the new entry.

on addSets_(sender)
		set my theData to {{backupset:"newBackupset", sourcefiles:{}}} & my theData
		set my theData to my theData
end addSets_

Somehow the new entry is seen as something foreign by writeToFile_atomically?

It isn’t the end of the world but makes me nervous. Any thoughts appreciated, or thoughts about a better archiving method.

Thanks, Rob

Try logging the value of theNewAnotherArray when you get the error. My guess is that having sourcefiles as {} is causing the problem, although I can’t see an obvious alternative.

The log shows something interesting

on applicationShouldTerminate_(sender)
		--before archiving, we need to clear out all the image objects 
		my removeImagesBeforeArchiving()
		my writeToFile()
		return true
end applicationShouldTerminate_

on removeImagesBeforeArchiving()
		set theOldArraysValues to Backset's arrangedObjects()
		repeat with i from 1 to count of theOldArraysValues
			set theSourceItems to Backset's arrangedObjects()'s objectAtIndex_(i - 1)'s valueForKey_("sourcefiles")
			repeat with j from 1 to count of theSourceItems
				set theSourceItemsObject to theSourceItems's objectAtIndex_(j - 1)
                                --here we remove the image and set fileicon to ""
				theSourceItemsObject's setValue_forKey_("", "fileicon")
			end repeat
		end repeat
		log theOldArraysValues
end removeImagesBeforeArchiving

You can see that, on terminate, the images are removed so the array can be archived (I tried NSCoding etc. with no luck.)

So when a new item is added to table 1 and you quit without editing that items name, it returns the array with images in it even though removeImagesBeforeArchiving() is called. Then the error happens. When you edit the new item in table 1 or do anything there, it returns the image-less array which is archived fine.

I have tried numerous ways of triggering the array into the current form but no luck yet.

Cheers, Rob

Can you try something like changing the selection when quitting?

I tried that and no luck but this works

on writeToFile()
		set theCurrentArrayValues to Backset's arrangedObjects()
		set theNewAnotherArray to NSMutableArray's alloc()'s init()
		theNewAnotherArray's addObjectsFromArray_(theCurrentArrayValues)
		if not theNewAnotherArray's writeToFile_atomically_(FILE_PATH, true) then
			set messageText to "There was an error writing to file"
			display dialog messageText buttons {"Ok"} default button 1
		end if
end writeToFile

That makes sense since I change the images to “” in Backset’s arrangedObjects(). It is theData variable that isn’t getting updated yet.

What I don’t understand is why this doesn’t work as well:

on writeToFile()
		set theCurrentArrayValues to Backset's arrangedObjects()
		if not theCurrentArrayValues y's writeToFile_atomically_(FILE_PATH, true) then
			set messageText to "There was an error writing to file"
			display dialog messageText buttons {"Ok"} default button 1
		end if
end writeToFile

Isn’t Backset’s arrangedObjects() an array? Why do I have to create new array and then add that to it to pass to writeToFile?

Well, It’s solved now. The archive works and the table bindings are incredibly fast, even with dozens of files and images added and with hardly any code!

Now if I can only add drag and drop to the table - so far no luck. There are six datasource methods! I may have to make aOBJ-C class for that one.

Thanks, Shane

Rob

Could it be that something requires an NSMutableArray and you were passing an immutable one?

I thought of that. I think I read somewhere that NSAArrayController’s arrangedObjects returns a plain Array… though you can change the values of it with setValue_forKey_ so maybe not. Is there a way in cocoa to log what class something is?

Or perhaps we need to pass the writeToFile an new instance of the Array and not the results of the array controller itself?

Rob