get value of tableview items from array controller

Guess who missed his own advice: make sure Handles Content as Compound Value is checked for the Array Controller’s binding!

Ok - it works. I think.

The two tables are now in sync without any weird behavior. I had to mess around with the array controller settings for the bindings and the attributes to get it right. I’ll post the result if it all looks good tomorrow.

Thanks Shane. If this works it will have saved me many lines of code.

Rob

Shane,

I thought I was out of the woods. The two tables are working perfectly in snyc.

In my app though, table 2 should be empty (no placeholders). Table one can have a placeholder representing the first set fine.

I have spent many hours trying to make this work.

First I tried initializing with this:

set my theData to {{backupset:"newset", sourcefiles:""}}

but weird things happen when you try to add new files to the list.

set sourcefiles of item actualSelectedRow of theOriginalData to :{{ filename:newFilename, filepath:newFilepath}}}

}

It adds the new row but then all the sets show the same new row in their list. The bugs go on…

double curly braces instead of “” in sourcefiles doesn’t work at all.

I also tried using the placeholder text and replacing it with the new data, which works but then there are other bugs.

Should we be using NSMutableArray instance instead of a plain record to send to OBJ-c array controllers?

Stumped, Rob

That’s what I’d expect to work, and does here in my (limited) testing.

Where does theOriginalData fit into the scheme of things?

I was using “my thedata” referring to the variable bound to the array controllers content. I tried setting theOriginalData to my theData and did the changes to that and then set my theData back to theOriginalData at the end. It seemed to work better that way. I tried both ways.

But using soucrefiles:“” and then trying to set soucrefiles in table 2 causes the all the sets in table 1 to also have added that record. You will see if you try it out.

Here is the project if you, or anyone else, wants to give it a try. It is just two tables that are bound. I commented out the “good” code if you want to see how it can run perfectly you can reconstitute the good code. As is - I set it up for allowing an empty table 2 which is what I am struggling with here.

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

Thanks, Rob

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.