Getting Started with AppleScript Studio - Table Views

By Ben Waldie

In this month’s column, we’re going to discuss a specific type of interface element that is often incorporated into AppleScript Studio projects’ a table view. Table views consist of one or more cells (displayed using columns and rows), which may be used to display data. Think of a table view as being similar in layout to a worksheet in Excel. See figure 1 for an example of a 2-column table view in an AppleScript Studio project’s interface.

Figure 1. A Table View in AppleScript Studio

Table views may be configured in a variety of ways via Interface Builder’s Inspector palette, depending on the exact needs of your specific project. For example, table views may be configured to display or hide their column headers, allow cells to be user editable, allow rows and columns to be resized and re-arranged, allow multiple selections, allow empty selections, and more. Cells within table views may even contain other types of interface elements, including checkboxes, popup buttons, text fields, and more.

How Table Views Work

Interface Builder itself does not provide a direct mechanism for populating table views with data within an AppleScript Studio project. This must actually be done via the AppleScript code within the project. We’ll get to this shortly. First, let’s talk briefly about how table views work.

Contrary to how they appear, table views don’t actually contain data themselves. Rather, table views are linked to data sources, which will contain the data. Table views are used to visually display the data within an associated data source. Data sources consist of data columns, data rows, and data cells. A data cell is essentially the intersection of a data row with a specific data column. For example, if a data source has two data columns, then each data row within the data source will contain two data cells, one for each column. Think of a data source as being kind of like a mini-database within the memory of your running AppleScript Studio project.

Adding a Table View to an Interface

The table view interface element may be found in the Cocoa Data Views palette in Interface Builder. To add it to an interface, drag it from the palette to the desired location on a window, panel, or drawer. While the table view is selected in the interface, you may modify its various attributes, such as the number of columns, the row height, and more, using the Inspector palette. See figure 2.

Figure 2. Table View Attributes

If you are working with multiple table views within an interface, you may want to apply AppleScript names to the table views, as you might do to other elements within your project’s interface. This may be done via the AppleScript pane in the Inspector palette, and doing so will allow you to more easily differentiate between table views throughout your AppleScript code, ensuring that the correct one is being targeted.

Note that, after selecting a table view in an interface, the AppleScript pane in the Inspector palette will actually reference a scroll view. See figure 3. You can assign an AppleScript name to a scroll view, if desired.

Figure 3. A Scroll View in AppleScript Studio

In AppleScript Studio, table views are contained within scroll views. Double click on the scroll view to select the actual table view. See figure 4. You can assign an AppleScript name to a table view, if desired.

Figure 4. A Table View in AppleScript Studio

Within a table view, you may also choose to select individual table columns, and assign AppleScript names to them, if desired. See figure 5.

Figure 5. Table Columns in AppleScript Studio

When working with table views, think of each element above as an individual interface element within a hierarchy. A scroll view contains a table view, which contains table columns. In your AppleScript code, you will target these interface elements within their interface hierarchy, for example table view 1 of scroll view 1 of window 1. Like any other interface element, each of these elements possesses its own attributes (check out the AppleScript Studio Terminology reference for a detailed overview of scriptable attributes for these elements), as well as various event handlers, which may be linked to the element, depending on the needs of your project.

Preparing to Follow Along

To follow along with the examples in this month’s column, you will need to create an AppleScript Studio project. Launch Xcode, and select New Project from the File menu. Next, select the AppleScript Application project template, and create a new project named Table Example.

Once the project has been created, double click on the MainMenu.nib component to open the project’s interface in Interface Builder. Next, design the main window of the project to match the example window shown in figure 6, below.

Figure 6. Table View Example Interface

Next, assign AppleScript names and clicked event handlers to the buttons on the interface. Assign the name add to the + button, the name remove to the - button, and the name reveal to the Reveal button. Link the clicked event handler for each of these buttons to the main script in your project. Finally, link the will open event handler for the window itself to the main script in your project.

Populating a Table View’s Data Source

You may have noticed that, in designing the interface above, we did not create a data source for our table view. Data sources can be manually inserted and linked to table views in Interface Builder. However, the release of AppleScript Studio 1.4 with Mac OS X 10.4 Tiger has virtually eliminated the need to do this. Beginning with AppleScript Studio 1.4, we can now set the content property of a table view to a list containing values, a list of lists containing values, or a list of records containing fields and values. Doing so will automatically create a data source (if it does not already exist) for the targeted table view, and insert and populate the appropriate number of data rows and columns, based on the data specified. Let’s take a look at this in more detail.

Suppose we are working with a single column table view, or suppose we only want to populate the first column of a multi-column table view. To do this, we can set the contents of the table view to a list of individual values. For example:

set content of table view 1 of scroll view 1 of window 1 to {"Macintosh HD", "Users", "bwaldie"}

See figure 7 for an example of how a table view would appear, after executing the code above.

Figure 7. Populating a Single Table Column Using a List

Now, suppose we want to populate both columns of a two-column table. This can be done by setting the content property of the table view to a list of two-item lists. For example:

set content of table view 1 of scroll view 1 of window 1 to {{"Macintosh HD", "Macintosh HD:"}, {"Users", "Macintosh HD:Users:"}, {"bwaldie", "Macintosh HD:Users:bwaldie:"}}

Figure 8 shows an example of how a table view would appear, after executing the code above.

Figure 8. Populating Multiple Table Columns Using a List of Lists

The following code will essentially perform the same function as the previous example. However, it is done using a list of records containing field names and values, rather than a list of lists. Notice that the field names specified in each record correspond to the AppleScript names that we applied to the table columns in our interface.

set content of table view 1 of scroll view 1 of window 1 to {{|name|:"Macintosh HD", |path|:"Macintosh HD:"}, {|name|:"Users", |path|:"Macintosh HD:Users:"}, {|name|:"bwaldie", |path|:"Macintosh HD:Users:bwaldie:"}}

Creating a Data Source

It is possible to write code to create a data source and link it to a table view manually, rather than allowing this to be done dynamically. To do this, first use the make command to create the data source. Then, set the data source of the table view to reference the newly created data source. For example:

set theDataSource to make new data source at end
set data source of table view 1 of scroll view 1 of window 1 to theDataSource

Adding Columns and Rows

Prior to AppleScript Studio 1.4, it was necessary to write code to build data columns and data rows within a table view’s data source. In fact, you may still find situations where you will want to do this. To create a data column or a data row within a data source, use the make command, as demonstrated here.

make new data column at end of data columns of theDataSource

Or

make new data row at end of data rows of theDataSource

Deleting Columns and Rows

Deleting data columns and data rows from a table view’s data source is done using the delete command. For example:

delete data row 1 of theDataSource

Or

delete data column 1 of theDataSource

Getting the Selection of a Table View

To access the selection of a table view, reference the selected data row or selected data rows properties of the table view. For example:

selected data row of table view 1 of scroll view 1 of window 1
--> data row id 4 of data source id 2

Pulling Things Together
Now, let’s pull together several of the techniques that we have discussed throughout this column. Enter the following example code into the main script of your AppleScript Studio project.

on clicked theObject
	if name of theObject = "add" then
		set theNewFile to choose file without invisibles
		tell data source of table view 1 of scroll view 1 of window 1
			if (count data columns) = 0 then
				repeat 2 times
					make new data column at end of data columns
				end repeat
			end if
			set theNewRow to make new data row at end of data rows
			set contents of data cell 1 of theNewRow to name of (info for theNewFile)
			set contents of data cell 2 of theNewRow to theNewFile as string
		end tell
	else if name of theObject = "delete" then
		if (count data rows of data source of table view 1 of scroll view 1 of window 1) is greater than 0 then
			set theSelectedRow to selected data row of table view 1 of scroll view 1 of window 1
			delete theSelectedRow
		end if
	else if name of theObject = "reveal" then
		if (count data rows of data source of table view 1 of scroll view 1 of window 1) is greater than 0 then
			set theSelectedRow to selected data row of table view 1 of scroll view 1 of window 1
			set thePath to contents of data cell 2 of theSelectedRow
			tell application "Finder" to reveal alias thePath
		end if
	end if
end clicked

on will open theObject
	set theFolder to choose folder without invisibles
	tell application "Finder"
		set theFiles to every file of theFolder
		set theTableViewContents to {}
		repeat with a from 1 to length of theFiles
			set theCurrentFile to item a of theFiles
			set theCurrentFileName to name of theCurrentFile
			set end of theTableViewContents to {theCurrentFileName, theCurrentFile as string}
		end repeat
	end tell
	set content of table view 1 of scroll view 1 of window 1 to theTableViewContents
end will open

Next, build and run the project. If all goes well, you should be prompted to choose a folder. Next, the project’s interface should be displayed, and the table view should be populated with the names and paths of any files that were found within the specified folder. Clicking the + button should prompt you to select a new file to add to the end of the table view. Clicking the button should remove the selected row from the table view. Clicking the Reveal button should display the file that is currently selected in the table view, in the Finder.

If you have any trouble following along, feel free to download a copy of the completed project here.

In Conclusion

You should now have a basic understanding of how to utilize table views in AppleScript Studio projects. For more guidance as you put the practices we have discussed to use, be sure to browse the AppleScript Studio Terminology Reference guide, included in Xcode’s documentation and on the Apple Developer Connection website. And, take some time to explore the example projects in the Developer/Examples/AppleScript Studio/ folder, installed with Xcode. Here, you will find numerous fully editable example projects that incorporate table views. Also, feel free to post your AppleScript Studio questions the AppleScript Studio forum on MacScripter’s online BBS.

Until next time just script it!

-Ben Waldie

Ben,

Thanks for the great tutorial…

After looking at the example (2 column) tables, I created 4 columns on the table in the scroll view, but I would like to be able add the columns to the NIB window dynamically.

I tried this below :

Added a fifth column “Flag” to the headers list, and a dummy value of “yes” to the corresponding data value for each row in the loop, but when I built and ran it only the first four data columns displayed.


-- This does not contain all the code, SEE the next script for the complete code I have so far
-- This did not work with the 4 column table while using the "repeat no_of_columns times"

on will open theObject
	set aColumnHeaders to {"SeqNo", "Flag", "Item Name", "Item Class", "Item Path"}
	set aColumnWidths to {60, 80, 180, 120,400}
	set no_of_columns to count aColumnHeaders
	set theDataSource to make new data source at end
	set data source of table view 1 of scroll view 1 of window 1 to theDataSource
	
	repeat no_of_columns times
		make new data column at end of data columns of theDataSource
	end repeat
	
	repeat loop...
		[...]
		set end of theTableViewContents to {counter, "yes", this_name, this_class, this_path}
	end repeat
end will open

The nice thing would be to have a NIB window ->Scroll View → Table with a couple of hardcoded columns, and build the remaining columns dynamically as the need arose. I dont want to build a Table with something like 10 fields, in order to use the table to display from 1 to 10 fields.

I am trying to build a dynamic table that I can call from applescript when “choose from list” is not enough. I noticed how you deleted columns dynamically, but did not find how they could be added on the fly.

I was able to :
( 1 ) assign the column headers, and column widths dynamically, but not add extra columns.
( 2 ) Double click on a row, and {open, reveal} the corresponding file/folder/symlink, etc, or take some other action.

As soon as I can figure it out, I would like to do the following :
( 1 ) Setup to allow sorting via the column headers.
( 2 ) Select a row and click a button to trigger an action (this should be fairly simple).
( 3 ) Call this app from a separate applescript, and pass it the 2-dimensional arrays to display in the table. (this might be harder) If I have no other choice I can write the data to a file, that this app looks for when it launches, and it can load the data there. There might be an easier way ?

Any ideas ?

Bill Hernandez
Plano, Texas


-- This contains what I have so far, and is working very well...

on will open theObject
	
	set aColumnHeaders to {"SeqNo", "Item Name", "Item Class", "Item Path"}
	set aColumnWidths to {60, 180, 120,400}
	set no_of_columns to count aColumnHeaders
	set theDataSource to make new data source at end
	set data source of table view 1 of scroll view 1 of window 1 to theDataSource
	
	repeat no_of_columns times
		make new data column at end of data columns of theDataSource
	end repeat
	
	set theFolder to choose folder without invisibles
	tell application "Finder"
		-- set theFiles to every file of theFolder as alias list
		-- set theFiles to every file of the entire contents of theFolder as alias list
		set theFiles to every item of the entire contents of theFolder as alias list		
	end tell
	set theTableViewContents to {}
	
	tell table view 1 of scroll view 1 of window 1
		repeat with counter from 1 to length of aColumnHeaders
			set the content of header cell of table column counter to (item counter of aColumnHeaders) as string
			set the width of table column counter to (item counter of aColumnWidths) as integer
		end repeat
	end tell

	set home_posix_path to POSIX path of (path to home folder from user domain as text)
	set len to count characters of home_posix_path
	if (last character of home_posix_path is equal to "/") then
		-- set len to len - 1
	end if
	set str2match to (characters 1 through len of home_posix_path) as string
	
	repeat with counter from 1 to length of theFiles
		set this_file to item counter of theFiles
		tell application "Finder" to set w_props to get properties of this_file
		set this_name to displayed name of w_props
		set this_class to (class of w_props) as string
		
		if (this_class is equal to "«class alia»") then
			set this_class to "alias"
		else if (this_class is equal to "«class cfol»") then
			set this_class to "folder"
		else if (this_class is equal to "«class docf»") then
			set this_class to "document"
		end if
		
		set this_path to POSIX path of (this_file as string)
		if (str2match is equal to ((characters 1 through len of this_path) as string)) then
			set this_path to "~" & (characters len through -1 of this_path) as string
		end if
		
		set end of theTableViewContents to {counter, this_name, this_class, this_path} -- this_file as string}
	end repeat
	set content of table view 1 of scroll view 1 of window 1 to theTableViewContents
	
end will open

on clicked theObject
	(*Add your script here.*)
	tell window of theObject
		close
	end tell
	tell application of theObject
		quit
	end tell
end clicked

on double clicked theObject
	(*Add your script here.*)
	if name of theObject = "table_view" then
		if ((count data rows of data source of table view 1 of scroll view 1 of window 1) is greater than 0) then
			set theSelectedRow to selected data row of table view 1 of scroll view 1 of window 1
			set thePath to contents of data cell 4 of theSelectedRow
			set thePath to do shell script "echo " & thePath

			-- THESE should be globals so they are only initialized once
			set b1 to "Reveal"
			set b2 to "Open"
			set s to "Which would you prefer ?" & return
			set s to s & "( 1 ) Reveal the item within its parent container." & return
			set s to s & "( 2 ) Open the item (file, or folder, etc)." & return & return

			set which_choice to button returned of (display dialog s buttons {b1, b2} default button {b2})
			
			tell application "Finder"
				activate
				if (which_choice is equal to b1) then
					reveal POSIX file thePath as alias
					set w_ref to Finder window 1
					select w_ref
					set toolbar visible of w_ref to false
					set w_props to {bounds:{10, 46, 300, 500}, current view:list view, sidebar width:0, toolbar visible:false, statusbar visible:true}
					set properties of w_ref to w_props
				else
					open POSIX file thePath as alias
				end if
			end tell
		end if
	end if
end double clicked