Finally, this is a more practical example. One common request is to have more than one entry field in a dialog. This will let you do that, with as many as you like (within reason and screen size, of course).
The alert handler is the same. The handler to build the view is passed a list of labels for the entry fields, and a matching number of placeholder strings for the fields. From this, it calculates how deep to make the accessory field -- each label-plus-field combination takes up 55 points -- and loops through building them.
You can see from the code that both labels and entry fields are NSTextFields -- the difference is in how their properties are set. Entry fields are editable, bordered, and draw a background. The labels get their stringValue set, while the entry fields get their placeholderString set. The entry fields also get tags, so they can be identified later.
The third handler takes theView and a tag, and returns the string entered in the field with that tag.
The calling code builds the view, shows the dialog, then loops through getting contents of the entry fields. The handlers that do the work can be stored in a script library, so again the code can be used under both Mavericks and Yosemite.
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"
-- check we are running in foreground
if not (current application's NSThread's isMainThread()) as boolean then
display alert "This script must be run from the main thread." buttons {"Cancel"} as critical
error number -128
end if
-- for styleNum, 0 = warning, 1 = informational, 2 = critical; for givingUpAfter, 0 means never; accessoryView of missing value means none
on displayAlert:mainText message:theExplanaton asStyle:styleNum buttons:buttonsList suppression:showSuppression givingUpAfter:giveUp accessoryView:theView
set buttonsList to reverse of buttonsList -- because they get added in reverse order cf AS
-- create an alert
set theAlert to current application's NSAlert's alloc()'s init()
-- set up alert
tell theAlert
its setAlertStyle:styleNum
its setMessageText:mainText
its setInformativeText:theExplanaton
repeat with anEntry in buttonsList
(its addButtonWithTitle:anEntry)
end repeat
its setShowsSuppressionButton:showSuppression
if theView is not missing value then its setAccessoryView:theView
end tell
-- if giveUp value > 0, tell the app to abort any modal event loop after that time, and thus close the panel
if giveUp > 0 then current application's NSApp's performSelector:"abortModal" withObject:(missing value) afterDelay:giveUp inModes:{current application's NSModalPanelRunLoopMode}
-- show alert in modal loop
set returnCode to theAlert's runModal()
-- if a giveUp time was specified and the alert didn't timeout, cancel the pending abort request
if giveUp > 0 and returnCode is not current application's NSModalResponseAbort then current application's NSObject's cancelPreviousPerformRequestsWithTarget:(current application's NSApp) selector:"abortModal" object:(missing value)
-- get values after alert is closed
set suppressedState to theAlert's suppressionButton()'s state() as boolean
set buttonNumber to returnCode mod 1000 + 1 -- where 1 = right-most button
if buttonNumber = 0 then
set buttonName to "Gave Up"
else
set buttonName to item buttonNumber of buttonsList
end if
return {buttonName, suppressedState}
end displayAlert:message:asStyle:buttons:suppression:givingUpAfter:accessoryView:
-- buld the accessory view
on accessoryViewWithLabels:listOfLabels andPlaceholders:listOfPlaceholders
set labelCount to count of listOfLabels
set totalDepth to labelCount * 55 -- each label plus entry field needs 55pts
set viewList to {} -- list of controls to add to accessory view
repeat with i from 1 to labelCount
set theLabel to (current application's NSTextField's alloc()'s initWithFrame:(current application's NSMakeRect(0, totalDepth - 30 - (i - 1) * 55, 400, 17)))
tell theLabel
(its setStringValue:(item i of listOfLabels))
(its setEditable:false)
(its setBordered:false)
(its setDrawsBackground:false)
end tell
copy theLabel to end of viewList
-- now text entry field
set theInput to (current application's NSTextField's alloc()'s initWithFrame:(current application's NSMakeRect(0, totalDepth - 55 - (i - 1) * 55, 400, 22)))
tell theInput
(its setEditable:true)
(its setBordered:true)
(its setPlaceholderString:(item i of listOfPlaceholders))
(its setTag:i)
end tell
copy theInput to end of viewList
end repeat
-- create a view and add items
set theView to current application's NSView's alloc()'s initWithFrame:(current application's NSMakeRect(0, 0, 400, totalDepth))
theView's setSubviews:viewList
return theView
end accessoryViewWithLabels:andPlaceholders:
-- retrieve the contents of the field with this tag
on textFromView:theView withTag:theTag
return (theView's viewWithTag:theTag)'s stringValue() as text
end textFromView:withTag:
-- Sample labels and entry placeholders
set listOfLabels to {"First name", "Initial", "First name", "Initial", "First name", "Initial", "Second name"}
set listOfPlaceholders to {"John", "Q", "John", "Q", "John", "Q", "Citizen"}
-- build the view
set theView to my accessoryViewWithLabels:listOfLabels andPlaceholders:listOfPlaceholders
-- pass the view to the alert handler
set {buttonName, suppressedState} to (my displayAlert:"Decision time" message:"Enter your name" asStyle:2 buttons:{"Cancel", "OK"} suppression:false givingUpAfter:120.0 accessoryView:theView)
-- retrieve the entered strings
set theAnswers to {}
repeat with i from 1 to count of listOfLabels
set end of theAnswers to (my textFromView:theView withTag:i)
end repeat
return {buttonName, theAnswers, suppressedState}