Tableview help

You change the value in the record/dictionary that represents that row.

You are binding the color to the array controller’s arrangedObjects, aren’t you?

Just to add, this behaviour seems common in some Mac admin tools such as Apple Remote Desktop & Casper Remote.

Yes

So you set the theColor property (or whatever you call it) of every item to current application’s NSColor’s blackColor() (or a property containing same. Then when you want one to appear in red, you set it’s theColor property to current application’s NSColor’s redColor().

Thanks Shane, still unclear how to isolate a row.

My binding for text colour of the text field cell in the table view is set to "selectedPKGsColour.’

If i change this to red, it changes the value for all text field cells.

So how do you tell it to only change the wanted rows colour?

Something like the below fails:

set my theItem's selectedPKGsColour to current application's NSColor's redColor()

As Shane mentioned before, the database of a table view is an array/list of dictionaries/records of the same type or an array/list of custom class objects.
A column is bound to a specific property. For example with this array of dictionaries

{ {thePath:"/blah/blah/blah", theColor:blackColor}, {thePath:"/foo/foo/foo", theColor:redColor} }
if you bind “thePath” to the value and the “theColor” to the text color of the text field of column 1 the table view will look like

/blah/blah/blah
/foo/foo/foo

so to change the color of a specific row you have to change the value of the respective “theColor” property in the array

Thanks both for your patience & perseverance.

I’ll see what i can do.

Sorry, still struggling. Code below.

script AEXAppDelegate
    
    property parent : class "NSObject"
    property theItem : {} -- the main property; array controller binds its content array to this
    
    -- IBOutlets
    property |window| : missing value
    property theArrayController : missing value
    property selectedPKGPath : missing value
    property defaults : missing value
    property testValue : missing value
    property certsAdded : 0
    property certsValue : missing value
    property selectedRow : missing value
    property selectedObjects : missing value
    property NSColor : class "NSColor" of current application
    property theColor : missing value
    property selectedPKGsColour : missing value
    property thePath : missing value
    property runNumber : 0
    property additionalPKGs : missing value
    
    on applicationWillFinishLaunching:aNotification
        
        set my selectedPKGsColour to current application's NSColor's blackColor()
        
        log selectedPKGsColour
        
        -- populate plist file with defaults (will not overwrite non-default settings))
        regDefaults_(me)
        
        -- retrieve plist values
        retrieveDefaults_(me)
        
        set my theItem to {thePath:additionalPKGs, theColor:selectedPKGsColour}
        
        log theItem
        
    end applicationWillFinishLaunching:
    
    on regDefaults_(sender)
        tell current application's NSUserDefaults to set defaults to standardUserDefaults()
        tell defaults to registerDefaults_({additionalPKGs:additionalPKGs}) as list
    end regDefaults_
    
    -- Get values from plist
    on retrieveDefaults_(sender)
        tell defaults to set my additionalPKGs to objectForKey_("additionalPKGs") as list
    end retrieveDefaults_
    
    on selectPKG_(sender)
        try
            
            set runNumber to runNumber + 1
            
            choose file of type {"com.apple.installer-package-archive"} with prompt "Select a .pkg to add:" default location (path to desktop folder)
            
            set my selectedPKGPath to POSIX path of result
            
            tell theArrayController to addObject:selectedPKGPath
            
            tell defaults to registerDefaults_({additionalPKGs:additionalPKGs}) as list
            
            set my theItem to theItem & {thePath:additionalPKGs, theColor:selectedPKGsColour}
            
            log theItem

            tell standardUserDefaults() of current application's NSUserDefaults
                
                setObject_forKey_(additionalPKGs, "additionalPKGs")
                
            end tell
            
        end try
        
        if runNumber is greater than 1
        
            set my selectedPKGsColour to current application's NSColor's redColor()
            
            log selectedPKGsColour
        
        end if
    
    end selectPKG_

    on deletePKG_(sender)
        
        tell theArrayController to set my selectedObjects to selectedObjects()
        
        log selectedObjects
        
        tell theArrayController to removeObjects:selectedObjects
        
        log "done"
        
        log additionalPKGs
        
        tell standardUserDefaults() of current application's NSUserDefaults
            
            setObject_forKey_(additionalPKGs, "additionalPKGs")
            
        end tell
        
        log "updated plist"
        
    end deletePKG_
    
    on testButton_(sender)
        
        repeat with theItem in additionalPKGs
            
            try
                -- Check for file
                do shell script "ls " & quoted form of theItem
                log "Found " & theItem
                
            on error
                
                log theItem & " is missing"
                
                display dialog "Cannot Find: " & additionalPKGs & ". Do you wish to proceed?" with icon 2 buttons {"No", "Yes"}
                
                -- Set missing items text to red.
                set my selectedPKGsColour to current application's NSColor's redColor()
                
            end try
            
        end repeat
        
    end testButton_
    
    on applicationShouldTerminate:sender
        -- Insert code here to do any housekeeping before your application quits
        return current application's NSTerminateNow
    end applicationShouldTerminate:
    
end script

How do I implement:

{
{thePath:"/blah/blah/blah", theColor:blackColor},
{thePath:"/foo/foo/foo", theColor:redColor}
}

I changed the array controllers content array bindings to a “Content Array For Mulitple Selection” which allows the app to compile.

In the bindings for “packages” column, i have the value bound to the array controller. The “Controller Key” is arrangedObjects & the “Model Key Path” is thePath.

I get the below in the apps logs, but nothing in the TableView.

If additionalPKGs is a list of paths, you need to loop through and make it a list of records. You’re just making a single record, with the color as one value, and the list of paths as the other.

Your problem is that you’re not building your array/list of records/dictionaries properly. Think of it as an AppleScript problem, not an ASObjC problem.

So a repeat loop that sets theItem?

I guess that would overwrite? Or as it’s an array, does it append?

Yes. When you read additionalPKGs, it’s presumably just a list of paths. What you want is a list/array of records/dictionaries, so you make one (off the top of my head):

set theBlack to current application's NSColor's blackColor()
set theList to {}
repeat with aValue in (additionalPKGs as list)
set end of theList to {thePath:aValue, theColor:theBlack}
end repeat
set my theItem to current application's NSMutableArray's arrayWithArray:theList

You don’t say how you’re going to trigger the change of color. If you’re checking that the files in the paths exists, you’d do that in the above repeat list, and use the red color if the item is missing.

Almost there, below is what I have:

It’s working, but not sure if there is a “better” way to read the arrays value & update the plist rather that the slight sledgehammer approach i’ve taken.

There are 3 other issues:

  1. How can I stop the array from having duplicate entries? << last “real” issue.
  2. How do I change the text for a certain row? (targeted by index, currently i delete the old object & re-add) << minor issue, could be resolved with the below.
  3. How can you keep the array in alphabetical order? sortedArrayUsingSelector:“localizedStandardCompare:” didn’t seem to work & I guess that is as we’re using an NSMutableArray.
script AEXAppDelegate
    
    property parent : class "NSObject"
    property theItem : {} -- the main property; array controller binds its content array to this
    
    -- IBOutlets
    property |window| : missing value
    property theArrayController : missing value
    property selectedPKGPath : missing value
    property defaults : missing value
    property aValue : missing value
    property selectedObjects : missing value
    property NSColor : class "NSColor" of current application
    property selectedPKGsColour : missing value
    property additionalPKGs : missing value
    property theList: {}
    property plistValue : {}
    property pkgsMissing : false
    
    on applicationWillFinishLaunching:aNotification

        -- populate plist file with defaults (will not overwrite non-default settings))
        regDefaults_(me)
        
        -- retrieve plist values
        retrieveDefaults_(me)
        
        set selectedPKGsColour to current application's NSColor's blackColor()

        repeat with aValue in (additionalPKGs as list)
            set end of theList to {thePath:aValue, theColor:selectedPKGsColour}
        end repeat
        set my theItem to current application's NSMutableArray's arrayWithArray:theList
        
    end applicationWillFinishLaunching:
    
    on regDefaults_(sender)
        tell current application's NSUserDefaults to set defaults to standardUserDefaults()
        tell defaults to registerDefaults_({additionalPKGs:additionalPKGs}) as list
    end regDefaults_
    
    -- Get values from plist
    on retrieveDefaults_(sender)
        tell defaults to set my additionalPKGs to objectForKey_("additionalPKGs") as list
    end retrieveDefaults_
    
    on selectPKG_(sender)
        
        set selectedPKGsColour to current application's NSColor's blackColor()
        
        try
            
            choose file of type {"com.apple.installer-package-archive"} with prompt "Select a .pkg to add:" default location (path to desktop folder)
            
            set my selectedPKGPath to POSIX path of result
            
            set theList to {thePath:selectedPKGPath, theColor:selectedPKGsColour}
            
            tell theArrayController to addObject:theList
            
            set plistValue to additionalPKGs as list
            
            set end of plistValue to selectedPKGPath

            tell standardUserDefaults() of current application's NSUserDefaults
                
                setObject_forKey_(plistValue, "additionalPKGs")
                
            end tell
            
        end try
    
    end selectPKG_

    on deletePKG_(sender)
        
        tell theArrayController to set my selectedObjects to selectedObjects()
        
        tell theArrayController to removeObjects:selectedObjects
        
        set plistValue to (theItem's valueForKey:"thePath") as list
        
        tell standardUserDefaults() of current application's NSUserDefaults
            
            setObject_forKey_(plistValue, "additionalPKGs")
            
        end tell

    end deletePKG_
    
    on testButton_(sender)
        
        repeat with selectedPKGPath in additionalPKGs
            
            try
                -- Check for file
                do shell script "ls " & quoted form of selectedPKGPath
                log "Found " & selectedPKGPath
                
            on error
                
                log selectedPKGPath & " is missing"
                
                set theList to {theColor:selectedPKGsColour, thePath:selectedPKGPath}
                
                tell theArrayController to removeObject:theList
                
                set my selectedPKGsColour to current application's NSColor's redColor()
                
                set theList to {thePath:selectedPKGPath, theColor:selectedPKGsColour}
                
                tell theArrayController to addObject:theList
                
                set pkgsMissing to true
                
            end try
            
        end repeat
        
        if pkgsMissing is true then
            
            display dialog "Cannot find one or more .pkgs. Do you wish to proceed?" with icon 2 buttons {"No", "Yes"}
        
        end if
        
    end testButton_

You need to check and stop it happening – there’s no magic button.

You set the property of the item fro that row.

You tell the array controller to do it.

Right, I guess there is a different sorting method for NSMutableArray compared to NSArray?

I’ve tried some iterations of:

set sortedArray to theItem's sortedArrayUsingSelector_("localizedCaseInsensitiveCompare:")

or

set my theData to theArrayController's sortedArrayUsingSelector:"localizedStandardCompare:"

but they both give:

you’re trying to sort a dictionary, which is not sortable.
Again, to display multiple rows in a table view the datasource must be an Array of Dictionaries in terms of ObjC. Each array contains the data for one table row

Thanks StefanK, so I’m back at square one… I’m really not following what it is I can do to try & change the order alphabetically.

change to applicationWillFinishLaunching() to


   on applicationWillFinishLaunching_(aNotification)
        regDefaults_(me)
        retrieveDefaults_(me)
  
        set selectedPKGsColour to current application's NSColor's blackColor()
        repeat with aValue in additionalPKGs
            theArrayController's addObject:{thePath:aValue, theColor:selectedPKGsColour}
	end repeat


    end applicationWillFinishLaunching_
 

The dictionary’s are added to the array controller directly.
As theItem “ I recommend to rename it to something more descriptive like pkgList “ is bound to the array controller the value is updated automatically

PS:

here some more refactored handlers


	on applicationWillFinishLaunching:aNotification
		tell current application's NSUserDefaults to set defaults to standardUserDefaults()
		
		-- populate plist file with defaults (will not overwrite non-default settings))
		regDefaults_(me)
		
		-- retrieve plist values
		set additionalPKGs to retrieveDefaults_(me)
		
		set selectedPKGsColour to current application's NSColor's blackColor()
		
		repeat with aValue in additionalPKGs
			(arrayController's addObject:{thePath:aValue, theColor:selectedPKGsColour})
		end repeat
		
	end applicationWillFinishLaunching:
	
	on regDefaults:sender
		tell defaults to registerDefaults:{additionalPKGs:{}}
	end regDefaults:
	
	-- Get values from plist
	on retrieveDefaults:sender
		tell defaults to return objectForKey_("additionalPKGs")
	end retrieveDefaults:
	
	on selectPKG:sender
		set selectedPKGsColour to current application's NSColor's blackColor()
		try
			choose file of type {"com.apple.installer-package-archive"} with prompt "Select a .pkg to add:" default location (path to desktop folder)
			set my selectedPKGPath to POSIX path of result
			set newPKG to {thePath:selectedPKGPath, theColor:selectedPKGsColour}
			tell theArrayController to addObject:newPKG
			tell standardUserDefaults() of current application's NSUserDefaults
				setObject_forKey_(theitems's valueForKey:"thePath", "additionalPKGs") -- valueForKey creates a list of all "thePath" values
			end tell
		on error e
			display dialog e
		end try
	end selectPKG:


Gents, thank you so much. I finally have something working as I want. This is a POC that will be added to my app (https://github.com/macmule/AutoCasperNBI) in the next commit.

Below is the code, process is below… Mostly it’s over 3 sections:

  1. Read in values from a plist.

  2. Check that the pkgs in the plist exist, if they do not change text colour to red.

  3. Populate array post check, with the text in black if pkg exists… red if not.

  4. Allow users to add pkgs.

  5. Stop users from adding duplicate pkgs.

  6. Update array in app & plist.

  7. Allow users to select & delete pkgs.

  8. Update array in app & plist.

In the app i’ll add handling for if the array in the plist is empty.

As to sorting the Array, i was being to precious about it. Why script it when the header can sort… allowing the user to select.

One bit to add to the app would be a rescan button, that would be bound to checkAdditionalPKGs, this would allow someone to connect an external drive that holds the PKGs & then re-check that the files exist without restarting the app.

script AEXAppDelegate
    
    property parent : class "NSObject"
    property theItem : {} -- the main property; array controller binds its content array to this
    
    -- IBOutlets
    property |window| : missing value
    property theArrayController : missing value
    property selectedPKGPath : missing value
    property defaults : missing value
    property selectedObjects : missing value
    property NSColor : class "NSColor" of current application
    property selectedPKGsColour : missing value
    property additionalPKGs : ""
    property theList: {}
    property plistValue : {}
    property pkgsMissing : false
    

----- ON START -----
    on applicationWillFinishLaunching:aNotification

        -- populate plist file with defaults (will not overwrite non-default settings))
        regDefaults_(me)
        
        -- retrieve plist values
        retrieveDefaults_(me)
        
        -- Check additional pkgs array, & amend accordingly
        checkAdditionalPKGs_(me)
   
    end applicationWillFinishLaunching:
    
    on regDefaults_(sender)
        tell current application's NSUserDefaults to set defaults to standardUserDefaults()
        tell defaults to registerDefaults_({additionalPKGs:additionalPKGs}) as list
    end regDefaults_
    
    -- Get values from plist
    on retrieveDefaults_(sender)
        tell defaults to set my additionalPKGs to objectForKey_("additionalPKGs") as list
    end retrieveDefaults_

----- THE MAIN PARTS -----

    -- Check additional pkgs array, & amend accordingly
    on checkAdditionalPKGs_(sender)
        
        repeat with selectedPKGPath in additionalPKGs
            
            try
                -- Check for file
                do shell script "ls " & quoted form of selectedPKGPath
                
                set selectedPKGsColour to current application's NSColor's blackColor()
                
                (theArrayController's addObject:{thePath:selectedPKGPath, theColor:selectedPKGsColour})
                
            on error
                
                log selectedPKGPath & " is missing"
                
                set theList to {theColor:selectedPKGsColour, thePath:selectedPKGPath}
                
                tell theArrayController to removeObject:theList
                
                set my selectedPKGsColour to current application's NSColor's redColor()
                
                (theArrayController's addObject:{thePath:selectedPKGPath, theColor:selectedPKGsColour})
                
                set pkgsMissing to true
                
            end try
            
        end repeat
        
        if pkgsMissing is true then
            
            --Logic to be added after sender to AutoCasperNBI if build button pressed.
            
        end if
        
    end checkAdditionalPKGs_

    -- Add PKG selected by user
    on selectPKG_(sender)
        
        set selectedPKGsColour to current application's NSColor's blackColor()
        
        try
            
            choose file of type {"com.apple.installer-package-archive"} with prompt "Select a .pkg to add:" default location (path to desktop folder)
            
            set my selectedPKGPath to POSIX path of result
            
           -- Do not add if a duplicate
           if (additionalPKGs does not contain selectedPKGPath) then
               
               set plistValue to additionalPKGs as list
               
               if plistValue is equal to missing value then
                   
                   set plistValue to selectedPKGPath
                   
                else
                   
                   set end of plistValue to selectedPKGPath
                   
               end if
               
               (theArrayController's addObject:{thePath:selectedPKGPath, theColor:selectedPKGsColour})
               
               tell standardUserDefaults() of current application's NSUserDefaults
                   
                   setObject_forKey_(plistValue, "additionalPKGs")
                   
               end tell
               
            else
            
                log "Duplicate selected"

            end if

        end try
        
    end selectPKG_

    -- Remove Selected PKG
    on deletePKG_(sender)
        
        tell theArrayController to set my selectedObjects to selectedObjects()
        
        tell theArrayController to removeObjects:selectedObjects
        
        set plistValue to (theItem's valueForKey:"thePath") as list
        
        tell standardUserDefaults() of current application's NSUserDefaults
            
            setObject_forKey_(plistValue, "additionalPKGs")
            
        end tell

    end deletePKG_

    -- On Terminate
    on applicationShouldTerminate:sender
        -- Insert code here to do any housekeeping before your application quits
        return current application's NSTerminateNow
    end applicationShouldTerminate:
    
end script

Again, thank you both so much.

This site, your input & Shane book & site have been massively helpful. Not just for this project, but over the years too.