Can different fields show/disappear depending on popup/checkbox choice in Dialog Toolkit?

Hello pjh,
this is a very nice solution that mostly seems to be configurable as I would like to.

At a first look, one thing that seems to be missing is a datepicker, which in my case would be very useful to avoid mistypings…hope that this can be done and I didn’t spot it in the documentation!

Edit: turns out that a basic way on inputing dates in swiftdialog has been added, if it works as I would expect this is definitively worth a try!

Sources for Dialog ToolKit Plus and SwiftDialog are available so that they can be modified/extended, like the DT+ extension that provides Date Pickers, although SwiftDialog is built with Xcode (and uses Swift). SwiftDialog (and pretty much any other external script/app) also has the same limitation in that once the dialog is running there isn’t a handy way to dynamically access the window or rearrange the controls (which for SwiftDialog are set using command line arguments or JSON). About the only way around that is to have your script/app create the entire UI by itself and be structured to provide that ability.

Dialog Toolkit does a lot of the heavy lifting for you, but if you wanted to use something else you can always use the Cocoa frameworks like regular applications do. AppleScriptObj-C can be used to access the Cocoa frameworks from AppleScript, and there are a few other languages that also provide access with varying amounts of hair loss such as JXA or PyObjC (I am staying away from Swift). In addition to knowing these languages and their development environments, you would need to get familiar with the Cocoa frameworks, so there is a bit of a learning curve (I am also deliberately staying away from Xcode, which has its own learning curve).

I’ve updated my previous script to (hopefully) make it a little easier to follow what it is doing.

1 Like

Thank you for your explanation, now I understand what you meant with “extending the library script”. Unfortunately it’s for sure way beyond my abilities to learn a programming language and how to develop using it.
I didin’t know SwiftDialog, so I tried it to see if it can suit my needs. After some very basic tests, turns out that is quite easy to setup a single UI like in DT, and I found an AS script that shows me how to run the dialog and get the results inside variables I can use later for other tasks. I also noticed that some elements can be changed on the fly while the dialog is already showing (titles, backgrounds, button names, window sizes), and I was very happy about this possibility, but I didn’t find any mention about showing/hiding checkboxes and/or textfields :frowning: I felt like being capped at 95% :smiley: . I opened an issue on GitHub to see if someone has a suggestion for this

About the script edit, I really appreciate it, and I will make for sure some experiments to see if I can edit to suit my needs!

Will report back if I have updates either from your script or SwiftDialog

I think that the updated script is almost a solution for me, since I was able to add checkboxes, date fields, textfields to different popup choices :heart_eyes:
Now I have another problem, but it’s due to my lack of knowledge and I’m sure that can be solved in one row addition:
How can I return the value of a single variable (let’s say B2_textfield)?
At the very end I will have something like:

tell application "Mail"
			activate
			set theContent to "Test content"
			set theMessage to make new outgoing message with properties {subject:"test", content:theContent, visible:true}

where I would like to have the value of B2_textfield as subject in the email instead of word “test”, how can I achieve this?

I don’t know the changes you have made, or if you have made matching changes to the formatResult handler (to extract other item values and add them to the result record) or the doStuff hander (to deal with the operation value and do stuff directly or pass the record to something else), so I will use my posted script.

Statements can be added to the doStuff handler to do stuff with the dialogValues record (which is the controlValues result from the DT+ window), or if there is a bunch of stuff to do, the record can be passed to another handler to do the stuff with it. In the posted script’s doStuff handler, the dialogResult record will have an operation label with a value of “That Thing” (the second item of popupItems), which evaluates to the That part of the if statement (note that this if statement is in a tell statement). This record would also have a B2_textfield labeled item, so in that handler, it would be something like:

-- snip --
else if it is second item of popupItems then -- perform `That` with dialogValues
    tell application "Mail"
        activate
        set theContent to "Test content"
        set theMessage to make new outgoing message with properties {subject:(B2_textField of dialogValues), content:theContent, visible:true}
    end tell
-- snip --

that worked, thank you

Now I have another problem, I think I figured out what’s the cause, but I don’t know how to solve :frowning:
This script goes into Automator, so it will be used as Finder action that takes input and passes it to the script

use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use script "Dialog Toolkit Plus" version "1.1"
use script "Dialog Toolkit Plus Extended" version "1.1" -- datePicker


property popupItems : {"A", "B", "C"} -- the operations to perform
global controlGroups -- this will be a dictionary of the alternate groups and their controls


on run {input, parameters}
	set controlGroups to current application's NSMutableDictionary's dictionary -- for using a keyPath
	set {viewWidth, theTop} to {400, 10}
	
	# controls are stacked from the bottom up for easier vertical positioning
	set {theButtons, minWidth} to create buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel" with equal widths
	if minWidth > viewWidth then set viewWidth to minWidth
	set {maxLabelWidth, controlLeft} to {100, 110}
	
	########## begin control group #1 - these are layered in the same space, with only the active alternate shown
	set newTop to theTop -- alternate A (This Thing)
	set {A2_textField, A2_labelField, newTop, fieldLeft} to create side labeled field ("") left inset 0 bottom 0 total width viewWidth label text "This #2:" field left controlLeft
	set {A1_textField, A1_labelField, newTop, fieldLeft} to create side labeled field ("") left inset 0 bottom (newTop + 12) total width viewWidth label text "This #1:" field left controlLeft
	set {dateLabelFieldA, theTop} to create label " Data:" left inset 60 bottom (newTop + 12) max width maxLabelWidth control size regular size
	set {datePickerA, theTop} to create textual datepicker textual stepper picker elements YearMonthDay initial date (current date) min date seconds 0 max date seconds 0 left inset controlLeft bottom (newTop + 8) extra width 0 extra height 0
	repeat with anItem in (addAlternate(1, first item of popupItems, {A1_textField, A1_labelField, A2_textField, A2_labelField, dateLabelFieldA, datePickerA}))
		(anItem's setHidden:false)
	end repeat
	----
	set newTop to theTop -- alternate B (That Thing)
	set {B3_checkbox, newTop, newWidth} to create checkbox "That Checkbox" bottom 0 max width (viewWidth / 2) left inset controlLeft without initial state
	set {B2_textField, B2_labelField, newTop, fieldLeft} to create side labeled field ("") left inset 0 bottom (newTop + 6) total width viewWidth label text "That #2:" field left controlLeft
	set {B1_textField, B1_labelField, newTop, fieldLeft} to create side labeled field ("") left inset 0 bottom (newTop + 6) total width viewWidth label text "That #1:" field left controlLeft
	repeat with anItem in (addAlternate(1, second item of popupItems, {B1_textField, B1_labelField, B2_textField, B2_labelField, B3_checkbox}))
		(anItem's setHidden:true)
	end repeat
	########## end control group #1
	
	set newTop to theTop -- alternate C (That Thing)
	set {C3_checkbox, newTop, newWidth} to create checkbox "That Checkbox C" bottom 0 max width (viewWidth / 2) left inset controlLeft without initial state
	set {C2_textField, C2_labelField, newTop, fieldLeft} to create side labeled field ("") left inset 0 bottom (newTop + 12) total width viewWidth label text "That #2:" field left controlLeft
	set {dateLabelFieldC, theTop} to create label " Data:" left inset 60 bottom (newTop + 12) max width maxLabelWidth control size regular size
	set {datePickerC, theTop} to create textual datepicker textual stepper picker elements YearMonthDay initial date (current date) min date seconds 0 max date seconds 0 left inset controlLeft bottom (newTop + 8) extra width 0 extra height 0
	set {C1_textField, C1_labelField, newTop, fieldLeft} to create side labeled field ("") left inset 0 bottom (newTop + 40) total width viewWidth label text "That #1:" field left controlLeft
	repeat with anItem in (addAlternate(1, third item of popupItems, {C1_textField, C1_labelField, C2_textField, C2_labelField, C3_checkbox, dateLabelFieldC, datePickerC}))
		(anItem's setHidden:true)
	end repeat
	########## end control group #1
	
	
	
	set {operationPopup, theTop} to create popup popupItems left inset controlLeft bottom 120 popup width 180 initial choice (first item of popupItems) --modifica il valore bottom
	operationPopup's setToolTip:"the operation to perform"
	operationPopup's setTarget:me
	operationPopup's setAction:"popupAction:" -- choose from group #1
	
	set allControls to {operationPopup, A1_labelField, A1_textField, A2_labelField, A2_textField, B1_labelField, B1_textField, B2_labelField, B2_textField, B3_checkbox, C1_textField, C1_labelField, C2_textField, C2_labelField, C3_checkbox, dateLabelFieldA, datePickerA, dateLabelFieldC, datePickerC}
	set {buttonName, controlValues} to display extended window "Descriptive Title" acc view width viewWidth acc view height theTop acc view controls allControls buttons theButtons without align cancel button
	
	doStuff for (formatResult from controlValues)
end run


# Extract the desired controls from the returned dialog values and group into a record.
to formatResult from dialogresult
	tell (date (item 17 of dialogresult) as «class isot» as string) to set ISODateA to text 9 thru 10 & "/" & text 6 thru 7 & "/" & text 1 thru 4 -- format date using the built-in ISO date class (no time zone info)
	tell (date (item 19 of dialogresult) as «class isot» as string) to set ISODateC to text 9 thru 10 & "/" & text 6 thru 7 & "/" & text 1 thru 4 -- format date using the built-in ISO date class (no time zone info)
	tell dialogresult -- use record for consistent key:value pairs
		if first item is first item of popupItems then -- common + alternate A
			set results to {operation:item 1, A1_textField:item 3, A2_textField:item 5, datePickerA:ISODateA}
		else if first item is second item of popupItems then -- common + alternate B
			set results to {operation:item 1, B1_textField:item 7, B2_textField:item 9, B3_checkbox:item 10}
		else if first item is third item of popupItems then
			set results to {operation:item 1, C1_textField:item 11, C2_textField:item 13, C3_checkbox:item 15, datePickerC:ISODateC}
		else -- oops
			set results to missing value
		end if
	end tell
	return results
end formatResult


# Add a set of alternate controls to the controlGroups dictionary - controls are returned for any customization.
to addAlternate((group as text), (keyName as text), (controls as list))
	if (controlGroups's valueForKey:group) is missing value then (controlGroups's setValue:(current application's NSMutableDictionary's dictionary()) forKey:group) -- add new group as needed
	controlGroups's setValue:(current application's NSDictionary's dictionaryWithDictionary:{controls:controls}) forKeyPath:(group & "." & keyName) -- add keyName alternate controls to group
	return controls
end addAlternate

# Action to change the visibility of group #1 controls based on the popup value.
on popupAction:sender
	repeat with aKey in popupItems -- hide everything
		set controlList to (controlGroups's valueForKeyPath:("1." & aKey & ".controls"))
		if controlList is not missing value then repeat with aControl in controlList
			((contents of aControl)'s setHidden:true)
		end repeat
	end repeat
	set controlList to (controlGroups's valueForKeyPath:("1." & ((sender's title) as text) & ".controls"))
	if controlList is not missing value then repeat with aControl in controlList -- show the selected control group
		((contents of aControl)'s setHidden:false)
	end repeat
end popupAction:

# Do stuff for the dialog result values.
to doStuff for dialogValues
	tell (operation of dialogValues) to if it is first item of popupItems then
		tell application "Mail"
			activate
			set theContent to "content"
			set theAttachments to input
			set theMessage to make new outgoing message with properties {subject:(A2_textField of dialogValues), content:theContent, visible:true}
			tell theMessage
				repeat with theAttachment in theAttachments
					make new attachment with properties {file name:theAttachment as alias} at after last paragraph
				end repeat
			end tell
		end tell
	else if it is second item of popupItems then
		tell application "Mail"
			activate
			set theContent to "content"
			set theAttachments to input
			set theMessage to make new outgoing message with properties {subject:(B2_textField of dialogValues), content:theContent, visible:true}
			tell theMessage
				repeat with theAttachment in theAttachments
					make new attachment with properties {file name:theAttachment as alias} at after last paragraph
				end repeat
			end tell
		end tell
	else if it is third item of popupItems then
		tell application "Mail"
			activate
			set theContent to "content"
			set theAttachments to input
			set theMessage to make new outgoing message with properties {subject:(C2_textField of dialogValues), content:theContent, visible:true}
			tell theMessage
				repeat with theAttachment in theAttachments
					make new attachment with properties {file name:theAttachment as alias} at after last paragraph
				end repeat
			end tell
		end tell
	end if
	return dialogValues
end doStuff

Variable input is not defined, and I think it’s because it’s used outside the main run handler. With (choose file) I could add an attachment, but it’s not how it’s supposed to work.

I hope that this is the last big problem I encounter; the remaining work will be the final adjustment of the dialog window with the correct textfields and checkboxes and transferring some subroutines that I used in the previous version of the script, so I think it should be all straightforward

Correct - the input parameter is local to the run handler. The easiest fix would probably be to save input into a global variable.

If you declare another global variable named theAttachments, and in the run handler set it to input, you could then remove all the set theAttachments to input statements in the doStuff handler.

This worked

Now that I should have all the tools I need, I will spend some time setting all up, but the good thing is that it’s a little more clear how the script works

Thank you for your time

I’m late to this discussion so, but hope this is still helpful. Back in 2017 and 2020 the idea of dynamically changing UI elements in DTP dialogs came up. Here are two of the discussions:

Sorry TL;DR – someone created this very nice example:

property ordersNmethods : {}

set ordersNmethods to {theOrders:{"Please Make a Selection", "Order Number 1, Production Method 2", "Order Number 2, Production Method 5"}, theMethods:{missing value, 2, 5}}
set {theButtons, minWidth} to create buttons {"Cancel", "OK"}
set {methodPopup, methodPopupLabel, theTop, matrixLeft} to create labeled popup {"1 - Some Method", "2 - Another Method", "3 - Still More Methods", "4 - The Rhythm Method", "5 - Methodical"} left inset 0 bottom 0 popup width 200 max width 400 label text "Override Method" popup left 0
set {orderPopup, orderPopupLabel, theTop, matrixLeft} to create labeled popup (theOrders of ordersNmethods) left inset 0 bottom (theTop + 20) popup width 200 max width 400 label text "Choose an Order" popup left 0
orderPopup's setTarget:me
orderPopup's setAction:"setMethod:"
set {buttonName, suppressedState, controlsResults} to display enhanced window "Test of a window" buttons theButtons giving up after 120 acc view width 700 acc view height (theTop + 10) acc view controls {methodPopup, orderPopup} without suppression
on setMethod:sender
	set selectedOrderIndex to (my orderPopup's indexOfSelectedItem() as integer) + 1
	set orderMethod to item selectedOrderIndex of theMethods of ordersNmethods
	my (methodPopup's selectItemAtIndex:(orderMethod - 1))
end setMethod:

Can confirm it still works in macOS Tahoe.

Thank you for your help Neophyte, I saved your links just in case I will need them in the future!

I am literally two steps away from completing my first version to test, but I am encountering a little difficulty: let’s say I want to convert A1_textField to a currency value. In my old script I used the suggestion from Apple website, but I can’t understand how to use here. I think it’s due to a wrong positioning of the function. This is the point where I am stuck, transferred to the example script:

use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use script "Dialog Toolkit Plus" version "1.1"
use script "Dialog Toolkit Plus Extended" version "1.1" -- datePicker


property popupItems : {"This Thing", "That Thing", "The Other Thing"} -- the operations to perform
global controlGroups -- this will be a dictionary of the alternate groups and their controls

on convertNumberToCurrencyString(theNumber)
	set theStyle to NSNumberFormatterCurrencyStyle of current application
	set theFormattedNumber to localizedStringFromNumber_numberStyle_(theNumber, theStyle) of NSNumberFormatter of current application
	return (theFormattedNumber as string)
end convertNumberToCurrencyString

on run -- example
	set controlGroups to current application's NSMutableDictionary's dictionary -- for using a keyPath
	set {viewWidth, theTop} to {400, 28}
	
	# controls are stacked from the bottom up for easier vertical positioning
	set {theButtons, minWidth} to create buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel" with equal widths
	if minWidth > viewWidth then set viewWidth to minWidth
	set {maxLabelWidth, controlLeft} to {100, 110}
	
	########## begin control group #1 - these are layered in the same space, with only the active alternate shown
	set newTop to theTop -- alternate A (This Thing)
	set {A2_textField, A2_labelField, newTop, fieldLeft} to create side labeled field ("") left inset 0 bottom (newTop) total width viewWidth label text "This #2:" field left controlLeft
	set {A1_textField, A1_labelField, newTop, fieldLeft} to create side labeled field ("") left inset 0 bottom (newTop + 6) total width viewWidth label text "This #1:" field left controlLeft
	repeat with anItem in (addAlternate(1, first item of popupItems, {A1_textField, A1_labelField, A2_textField, A2_labelField}))
		(anItem's setHidden:true)
	end repeat
	----
	set newTop to theTop -- alternate B (That Thing)
	set {B3_checkbox, newTop, newWidth} to create checkbox "That Checkbox" bottom (newTop - 24) max width (viewWidth / 2) left inset controlLeft without initial state
	set {B2_textField, B2_labelField, newTop, fieldLeft} to create side labeled field ("") left inset 0 bottom (newTop + 6) total width viewWidth label text "That #2:" field left controlLeft
	set {B1_textField, B1_labelField, newTop, fieldLeft} to create side labeled field ("") left inset 0 bottom (newTop + 6) total width viewWidth label text "That #1:" field left controlLeft
	repeat with anItem in (addAlternate(1, second item of popupItems, {B1_textField, B1_labelField, B2_textField, B2_labelField, B3_checkbox}))
		(anItem's setHidden:true)
	end repeat
	########## end control group #1
	
	set theTop to newTop -- continue from previous top
	set {divider, theTop} to create rule (theTop + 12) rule width viewWidth -- NSBox
	set {invoiceTextField, invoiceLabelField, theTop, fieldLeft} to create side labeled field ("") bottom (theTop + 12) total width (viewWidth / 2) label text "Invoice number:" field left controlLeft
	set {contractorTextField, contractorLabelField, theTop, fieldLeft} to create side labeled field ("") bottom (theTop + 12) total width viewWidth label text "Contractor:" field left controlLeft
	set {clientTextField, clientLabelField, theTop, fieldLeft} to create side labeled field ("") bottom (theTop + 12) total width viewWidth label text "Client:" field left controlLeft
	set {dateLabelField, theTop} to create label " Date:" left inset 60 bottom (theTop + 12) max width maxLabelWidth control size regular size
	set {datePicker, theTop} to create textual datepicker textual stepper picker elements YearMonthDay initial date (current date) min date seconds 0 max date seconds 0 left inset controlLeft bottom (theTop - 20) extra width 0 extra height 0
	
	set {operationPopup, theTop} to create popup popupItems left inset controlLeft bottom (theTop + 8) popup width 180 initial choice (last item of popupItems)
	operationPopup's setToolTip:"the operation to perform"
	operationPopup's setTarget:me
	operationPopup's setAction:"popupAction:" -- choose from group #1
	
	
	set allControls to {operationPopup, dateLabelField, datePicker, clientLabelField, clientTextField, contractorLabelField, contractorTextField, invoiceLabelField, invoiceTextField, divider, A1_labelField, A1_textField, A2_labelField, A2_textField, B1_labelField, B1_textField, B2_labelField, B2_textField, B3_checkbox} -- control references are listed from top to bottom for easier indexing
	set {buttonName, controlValues} to display extended window "Descriptive Title" acc view width viewWidth acc view height theTop acc view controls allControls buttons theButtons without align cancel button
	doStuff for (formatResult from controlValues)
end run

# Extract the desired controls from the returned dialog values and group into a record.
to formatResult from dialogResult
	tell (date (item 3 of dialogResult) as «class isot» as string) to set ISODate to text 9 thru 10 & "/" & text 6 thru 7 & "/" & text 1 thru 4 -- format date using the built-in ISO date class (no time zone info)
	tell dialogResult -- use record for consistent key:value pairs
		set common to {operation:item 1, theDate:ISODate, client:item 5, contractor:item 7, invoice:item 9} -- common items
		if first item is first item of popupItems then -- common + alternate A
			set results to common & {A1_textField:item 12, A2_textField:item 14}
		else if first item is second item of popupItems then -- common + alternate B
			set results to common & {B1_textField:item 16, B2_textField:item 18, B3_checkbox:item 19}
		else if first item is third item of popupItems then
			set results to common
		else -- oops
			set results to missing value
		end if
	end tell
	return results
end formatResult

# Add a set of alternate controls to the controlGroups dictionary - controls are returned for any customization.
to addAlternate((group as text), (keyName as text), (controls as list))
	if (controlGroups's valueForKey:group) is missing value then (controlGroups's setValue:(current application's NSMutableDictionary's dictionary()) forKey:group) -- add new group as needed
	controlGroups's setValue:(current application's NSDictionary's dictionaryWithDictionary:{controls:controls}) forKeyPath:(group & "." & keyName) -- add keyName alternate controls to group
	return controls
end addAlternate

# Action to change the visibility of group #1 controls based on the popup value.
on popupAction:sender
	repeat with aKey in popupItems -- hide everything
		set controlList to (controlGroups's valueForKeyPath:("1." & aKey & ".controls"))
		if controlList is not missing value then repeat with aControl in controlList
			((contents of aControl)'s setHidden:true)
		end repeat
	end repeat
	set controlList to (controlGroups's valueForKeyPath:("1." & ((sender's title) as text) & ".controls"))
	if controlList is not missing value then repeat with aControl in controlList -- show the selected control group
		((contents of aControl)'s setHidden:false)
	end repeat
end popupAction:

# Do stuff for the dialog result values.
to doStuff for dialogValues
	tell (operation of dialogValues) to if it is first item of popupItems then
		return convertNumberToCurrencyString((A2_textField of dialogValues))
	else if it is second item of popupItems then
		-- perform `That` with dialogValues for that operation
	else if it is third item of popupItems then
		-- perform `The Other` with dialogValues for the other operation
	end if
	return dialogValues
end doStuff

The second step will probably be: how can I check if the input entered in A2_textfield is a number (was not mistyped inserting a letter) and, if it’s not, give an error dialog and get back to the main dialog window?
Thank you very much

There are a couple of issues. The first is that in the doStuff handler, the if statement is in a tell statement, so the call to the convertNumberToCurrencyString handler needs to be be used with my or of me, and you are using the localizedStringFromNumber method, which wants a number, so the textField will need to be coerced to a number. Maybe you were also going to use numberFromString?

The second step is a bit more problematic, in that by the time the doStuff handler is called, the modal dialog is completed, so there is no going back.

A controlTextDidChange delegate method could be used with the textField to only accept numeric (or whatever) characters in the first place, and a controlTextDidEndEditing delegate method could be used when the editing is complete so that you can check to see if the stringValue formats correctly, clearing the textField and putting up an alert if it doesn’t. Both would be used while the dialog is still running.

Thank you, this worked perfectly (yes, I already had the conversion to number starting from a string)

Is this hard to implement? Would this option maintain all other entered fields so I can only edit the mistyped fields? Otherwise if I would have to enter again all fields it’s not worth the effort, and an error dialog explaining what happened is enough. This is what I came up with in the sample script, with a dialog showing the error if the field is not a number, did I write it correctly? It works, so it should be not so shabby :slight_smile:

use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use script "Dialog Toolkit Plus" version "1.1"
use script "Dialog Toolkit Plus Extended" version "1.1" -- datePicker


property popupItems : {"This Thing", "That Thing", "The Other Thing"} -- the operations to perform
global controlGroups -- this will be a dictionary of the alternate groups and their controls

on number_to_string(this_number)
	set this_number to this_number as string
	if this_number contains "E+" then
		set x to the offset of "," in this_number
		set y to the offset of "+" in this_number
		set z to the offset of "E" in this_number
		set the decimal_adjust to characters (y - (length of this_number)) thru -1 of this_number as string as number
		if x is not 0 then
			set the first_part to characters 1 thru (x - 1) of this_number as string
		else
			set the first_part to ""
		end if
		set the second_part to characters (x + 1) thru (z - 1) of this_number as string
		set the converted_number to the first_part
		repeat with i from 1 to the decimal_adjust
			try
				set the converted_number to the converted_number & character i of the second_part
			on error
				set the converted_number to the converted_number & "0"
			end try
		end repeat
		return the converted_number
	else if this_number contains "." then
		set AppleScript's text item delimiters to "."
		set arrayObjects to text items of this_number
		set AppleScript's text item delimiters to ","
		set this_number to arrayObjects as string
		set AppleScript's text item delimiters to ""
		return this_number
	else
		return this_number
	end if
end number_to_string

on convertNumberToCurrencyString(theNumber)
	set theStyle to NSNumberFormatterCurrencyStyle of current application
	set theFormattedNumber to localizedStringFromNumber_numberStyle_(theNumber, theStyle) of NSNumberFormatter of current application
	return (theFormattedNumber as string)
end convertNumberToCurrencyString

on run -- example
	set controlGroups to current application's NSMutableDictionary's dictionary -- for using a keyPath
	set {viewWidth, theTop} to {400, 28}
	
	# controls are stacked from the bottom up for easier vertical positioning
	set {theButtons, minWidth} to create buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel" with equal widths
	if minWidth > viewWidth then set viewWidth to minWidth
	set {maxLabelWidth, controlLeft} to {100, 110}
	
	########## begin control group #1 - these are layered in the same space, with only the active alternate shown
	set newTop to theTop -- alternate A (This Thing)
	set {A2_textField, A2_labelField, newTop, fieldLeft} to create side labeled field ("") left inset 0 bottom (newTop) total width viewWidth label text "This #2:" field left controlLeft
	set {A1_textField, A1_labelField, newTop, fieldLeft} to create side labeled field ("") left inset 0 bottom (newTop + 6) total width viewWidth label text "This #1:" field left controlLeft
	repeat with anItem in (addAlternate(1, first item of popupItems, {A1_textField, A1_labelField, A2_textField, A2_labelField}))
		(anItem's setHidden:true)
	end repeat
	----
	set newTop to theTop -- alternate B (That Thing)
	set {B3_checkbox, newTop, newWidth} to create checkbox "That Checkbox" bottom (newTop - 24) max width (viewWidth / 2) left inset controlLeft without initial state
	set {B2_textField, B2_labelField, newTop, fieldLeft} to create side labeled field ("") left inset 0 bottom (newTop + 6) total width viewWidth label text "That #2:" field left controlLeft
	set {B1_textField, B1_labelField, newTop, fieldLeft} to create side labeled field ("") left inset 0 bottom (newTop + 6) total width viewWidth label text "That #1:" field left controlLeft
	repeat with anItem in (addAlternate(1, second item of popupItems, {B1_textField, B1_labelField, B2_textField, B2_labelField, B3_checkbox}))
		(anItem's setHidden:true)
	end repeat
	########## end control group #1
	
	set theTop to newTop -- continue from previous top
	set {divider, theTop} to create rule (theTop + 12) rule width viewWidth -- NSBox
	set {invoiceTextField, invoiceLabelField, theTop, fieldLeft} to create side labeled field ("") bottom (theTop + 12) total width (viewWidth / 2) label text "Invoice number:" field left controlLeft
	set {contractorTextField, contractorLabelField, theTop, fieldLeft} to create side labeled field ("") bottom (theTop + 12) total width viewWidth label text "Contractor:" field left controlLeft
	set {clientTextField, clientLabelField, theTop, fieldLeft} to create side labeled field ("") bottom (theTop + 12) total width viewWidth label text "Client:" field left controlLeft
	set {dateLabelField, theTop} to create label " Date:" left inset 60 bottom (theTop + 12) max width maxLabelWidth control size regular size
	set {datePicker, theTop} to create textual datepicker textual stepper picker elements YearMonthDay initial date (current date) min date seconds 0 max date seconds 0 left inset controlLeft bottom (theTop - 20) extra width 0 extra height 0
	
	set {operationPopup, theTop} to create popup popupItems left inset controlLeft bottom (theTop + 8) popup width 180 initial choice (last item of popupItems)
	operationPopup's setToolTip:"the operation to perform"
	operationPopup's setTarget:me
	operationPopup's setAction:"popupAction:" -- choose from group #1
	
	
	set allControls to {operationPopup, dateLabelField, datePicker, clientLabelField, clientTextField, contractorLabelField, contractorTextField, invoiceLabelField, invoiceTextField, divider, A1_labelField, A1_textField, A2_labelField, A2_textField, B1_labelField, B1_textField, B2_labelField, B2_textField, B3_checkbox} -- control references are listed from top to bottom for easier indexing
	set {buttonName, controlValues} to display extended window "Descriptive Title" acc view width viewWidth acc view height theTop acc view controls allControls buttons theButtons without align cancel button
	doStuff for (formatResult from controlValues)
end run

# Extract the desired controls from the returned dialog values and group into a record.
to formatResult from dialogResult
	tell (date (item 3 of dialogResult) as «class isot» as string) to set ISODate to text 9 thru 10 & "/" & text 6 thru 7 & "/" & text 1 thru 4 -- format date using the built-in ISO date class (no time zone info)
	tell dialogResult -- use record for consistent key:value pairs
		set common to {operation:item 1, theDate:ISODate, client:item 5, contractor:item 7, invoice:item 9} -- common items
		if first item is first item of popupItems then -- common + alternate A
			set results to common & {A1_textField:item 12, A2_textField:item 14}
		else if first item is second item of popupItems then -- common + alternate B
			set results to common & {B1_textField:item 16, B2_textField:item 18, B3_checkbox:item 19}
		else if first item is third item of popupItems then
			set results to common
		else -- oops
			set results to missing value
		end if
	end tell
	return results
end formatResult

# Add a set of alternate controls to the controlGroups dictionary - controls are returned for any customization.
to addAlternate((group as text), (keyName as text), (controls as list))
	if (controlGroups's valueForKey:group) is missing value then (controlGroups's setValue:(current application's NSMutableDictionary's dictionary()) forKey:group) -- add new group as needed
	controlGroups's setValue:(current application's NSDictionary's dictionaryWithDictionary:{controls:controls}) forKeyPath:(group & "." & keyName) -- add keyName alternate controls to group
	return controls
end addAlternate

# Action to change the visibility of group #1 controls based on the popup value.
on popupAction:sender
	repeat with aKey in popupItems -- hide everything
		set controlList to (controlGroups's valueForKeyPath:("1." & aKey & ".controls"))
		if controlList is not missing value then repeat with aControl in controlList
			((contents of aControl)'s setHidden:true)
		end repeat
	end repeat
	set controlList to (controlGroups's valueForKeyPath:("1." & ((sender's title) as text) & ".controls"))
	if controlList is not missing value then repeat with aControl in controlList -- show the selected control group
		((contents of aControl)'s setHidden:false)
	end repeat
end popupAction:

# Do stuff for the dialog result values.
to doStuff for dialogValues
	tell (operation of dialogValues) to if it is first item of popupItems then
		try
			return my convertNumberToCurrencyString(my number_to_string(A2_textField of dialogValues) as number)
		on error
			tell application "System Events"
				display dialog "Not a number!" with icon 0 buttons {"Cancel"} default button "Cancel"
			end tell
			error number -128
		end try
	else if it is second item of popupItems then
		-- perform `That` with dialogValues for that operation
	else if it is third item of popupItems then
		-- perform `The Other` with dialogValues for the other operation
	end if
	return dialogValues
end doStuff

Delegate handlers can be implemented by setting the delegate for the particular textField and including the handler - other textFields will not be affected. The object sending the notification is also passed to the delegate handler for comparison, in the event you are doing different things for different objects. These handlers are called when the textField is changed and/or the editing is completed, so you can perform normal editing while the dialog is running. When the dialog exits, the item values via the doStuff handler can be used normally, as they would already be processed.

To enable the delegate handler(s), the delegate for the textField can be set when it is created, for example:

A2_textField's setDelegate:me

Any delegate method(s) are automatically called by adding them to the script. For the controlTextDidChange handler, it can be something like the following (set acceptable characters as desired):

# Check out additions to the number formatted text field.
on controlTextDidChange:notificationObject
    set acceptable to characters of "1234567890E,+-" -- or whatever
    set object to notificationObject's object -- the textField that posted the notification
    set theText to object's stringValue as text
    repeat with aCharacter in (characters of theText) -- just check everything in case something was pasted
        considering case -- for the exponent indicator `E`
            if aCharacter is not in acceptable then -- indicate and remove
                current application's NSBeep()
                set {prevTID, AppleScript's text item delimiters} to {AppleScript's text item delimiters, aCharacter}
                set {textItems, AppleScript's text item delimiters} to {text items of theText, ""}
                set {theText, AppleScript's text item delimiters} to {textItems as text, prevTID}
                (object's setStringValue:theText)
            end if
        end considering
    end repeat
end controlTextDidChange:

For the controlTextDidEndEditing handler, something like the following can be used. Note that the completion of any editing needs to be indicated by exiting the textField, for example tabbing to move to another control - the notification will not be sent if you were to immediately click on the dialog completion button while still in the textField.

# Verify the number in the formatted text field after editing is complete.
on controlTextDidEndEditing:notificationObject
    set object to notificationObject's object -- the textField that posted the notification
    set theText to object's stringValue as text
    -- check formatting of theText as desired, if failure then clear the textField and show an error alert
end controlTextDidEndEditing:

Thank you very much red_menace.

I tried both delegate methods and, If I understood well, in this particular case I have to choose between the two, since having both doesn’t make sense: if I’m prevented to add non acceptable characters from the beginning it doesn’t make sense to me to check the textfield after the editing, right?

I added the first one (controltextdidchange) and it works nice, but I don’t understand how to get an error notification (I can hear NSBeep ()); I tried to insert a display dialog but it feels kinda laggy after first error, and I have to exit and enter again the textfield to write again any character. This is how I updated the test script:

use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use script "Dialog Toolkit Plus" version "1.1"
use script "Dialog Toolkit Plus Extended" version "1.1" -- datePicker


property popupItems : {"This Thing", "That Thing", "The Other Thing"} -- the operations to perform
global controlGroups -- this will be a dictionary of the alternate groups and their controls

# Check out additions to the number formatted text field.
on controlTextDidChange:notificationObject
	set acceptable to characters of "1234567890E,+- " -- or whatever
	set object to notificationObject's object -- the textField that posted the notification
	set theText to object's stringValue as text
	repeat with aCharacter in (characters of theText) -- just check everything in case something was pasted
		considering case -- for the exponent indicator `E`
			if aCharacter is not in acceptable then -- indicate and remove
				current application's NSBeep()
				set {prevTID, AppleScript's text item delimiters} to {AppleScript's text item delimiters, aCharacter}
				set {textItems, AppleScript's text item delimiters} to {text items of theText, ""}
				set {theText, AppleScript's text item delimiters} to {textItems as text, prevTID}
				(object's setStringValue:theText)
				tell application "System Events"
					display dialog "Character not acceptable" with icon caution
				end tell
			end if
		end considering
	end repeat
end controlTextDidChange:

on run -- example
	set controlGroups to current application's NSMutableDictionary's dictionary -- for using a keyPath
	set {viewWidth, theTop} to {400, 28}
	
	# controls are stacked from the bottom up for easier vertical positioning
	set {theButtons, minWidth} to create buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel" with equal widths
	if minWidth > viewWidth then set viewWidth to minWidth
	set {maxLabelWidth, controlLeft} to {100, 110}
	
	########## begin control group #1 - these are layered in the same space, with only the active alternate shown
	set newTop to theTop -- alternate A (This Thing)
	set {A2_textField, A2_labelField, newTop, fieldLeft} to create side labeled field ("") left inset 0 bottom (newTop) total width viewWidth label text "This #2:" field left controlLeft
	set {A1_textField, A1_labelField, newTop, fieldLeft} to create side labeled field ("") left inset 0 bottom (newTop + 6) total width viewWidth label text "This #1:" field left controlLeft
	repeat with anItem in (addAlternate(1, first item of popupItems, {A1_textField, A1_labelField, A2_textField, A2_labelField}))
		(anItem's setHidden:true)
	end repeat
	----
	set newTop to theTop -- alternate B (That Thing)
	set {B3_checkbox, newTop, newWidth} to create checkbox "That Checkbox" bottom (newTop - 24) max width (viewWidth / 2) left inset controlLeft without initial state
	set {B2_textField, B2_labelField, newTop, fieldLeft} to create side labeled field ("") left inset 0 bottom (newTop + 6) total width viewWidth label text "That #2:" field left controlLeft
	set {B1_textField, B1_labelField, newTop, fieldLeft} to create side labeled field ("") left inset 0 bottom (newTop + 6) total width viewWidth label text "That #1:" field left controlLeft
	repeat with anItem in (addAlternate(1, second item of popupItems, {B1_textField, B1_labelField, B2_textField, B2_labelField, B3_checkbox}))
		(anItem's setHidden:true)
	end repeat
	########## end control group #1
	
	set theTop to newTop -- continue from previous top
	set {divider, theTop} to create rule (theTop + 12) rule width viewWidth -- NSBox
	set {invoiceTextField, invoiceLabelField, theTop, fieldLeft} to create side labeled field ("") bottom (theTop + 12) total width (viewWidth / 2) label text "Invoice number:" field left controlLeft
	set {contractorTextField, contractorLabelField, theTop, fieldLeft} to create side labeled field ("") bottom (theTop + 12) total width viewWidth label text "Contractor:" field left controlLeft
	set {clientTextField, clientLabelField, theTop, fieldLeft} to create side labeled field ("") bottom (theTop + 12) total width viewWidth label text "Client:" field left controlLeft
	set {dateLabelField, theTop} to create label " Date:" left inset 60 bottom (theTop + 12) max width maxLabelWidth control size regular size
	set {datePicker, theTop} to create textual datepicker textual stepper picker elements YearMonthDay initial date (current date) min date seconds 0 max date seconds 0 left inset controlLeft bottom (theTop - 20) extra width 0 extra height 0
	
	set {operationPopup, theTop} to create popup popupItems left inset controlLeft bottom (theTop + 8) popup width 180 initial choice (last item of popupItems)
	operationPopup's setToolTip:"the operation to perform"
	operationPopup's setTarget:me
	operationPopup's setAction:"popupAction:" -- choose from group #1
	A2_textField's setDelegate:me
	
	set allControls to {operationPopup, dateLabelField, datePicker, clientLabelField, clientTextField, contractorLabelField, contractorTextField, invoiceLabelField, invoiceTextField, divider, A1_labelField, A1_textField, A2_labelField, A2_textField, B1_labelField, B1_textField, B2_labelField, B2_textField, B3_checkbox} -- control references are listed from top to bottom for easier indexing
	set {buttonName, controlValues} to display extended window "Descriptive Title" acc view width viewWidth acc view height theTop acc view controls allControls buttons theButtons without align cancel button
	doStuff for (formatResult from controlValues)
end run

# Extract the desired controls from the returned dialog values and group into a record.
to formatResult from dialogResult
	tell (date (item 3 of dialogResult) as «class isot» as string) to set ISODate to text 9 thru 10 & "/" & text 6 thru 7 & "/" & text 1 thru 4 -- format date using the built-in ISO date class (no time zone info)
	tell dialogResult -- use record for consistent key:value pairs
		set common to {operation:item 1, theDate:ISODate, client:item 5, contractor:item 7, invoice:item 9}
		if first item is first item of popupItems then -- common + alternate A
			set results to common & {A1_textField:item 12, A2_textField:item 14}
		else if first item is second item of popupItems then -- common + alternate B
			set results to common & {B1_textField:item 16, B2_textField:item 18, B3_checkbox:item 19}
		else if first item is third item of popupItems then
			set results to common
		else -- oops
			set results to missing value
		end if
	end tell
	return results
end formatResult

# Add a set of alternate controls to the controlGroups dictionary - controls are returned for any customization.
to addAlternate((group as text), (keyName as text), (controls as list))
	if (controlGroups's valueForKey:group) is missing value then (controlGroups's setValue:(current application's NSMutableDictionary's dictionary()) forKey:group) -- add new group as needed
	controlGroups's setValue:(current application's NSDictionary's dictionaryWithDictionary:{controls:controls}) forKeyPath:(group & "." & keyName) -- add keyName alternate controls to group
	return controls
end addAlternate

# Action to change the visibility of group #1 controls based on the popup value.
on popupAction:sender
	repeat with aKey in popupItems -- hide everything
		set controlList to (controlGroups's valueForKeyPath:("1." & aKey & ".controls"))
		if controlList is not missing value then repeat with aControl in controlList
			((contents of aControl)'s setHidden:true)
		end repeat
	end repeat
	set controlList to (controlGroups's valueForKeyPath:("1." & ((sender's title) as text) & ".controls"))
	if controlList is not missing value then repeat with aControl in controlList -- show the selected control group
		((contents of aControl)'s setHidden:false)
	end repeat
end popupAction:

# Do stuff for the dialog result values.
to doStuff for dialogValues
	tell (operation of dialogValues) to if it is first item of popupItems then
		-- This
	else if it is second item of popupItems then
		-- That
	else if it is third item of popupItems then
		-- The Other
	end if
	return dialogValues
end doStuff

As I told you I tried the only method I know - display dialog - but it doesn’t perform well, but I’m sure there is a method for this

Second question: is the

A2_textField's setDelegate:me

pasted in the right position? It works, but I don’t know if there is a more appropriate place to paste.

Third question: just in case I would like to use this method to limit another textfield, let’s say A1, with different characters (let’s say I would like it to accept only letters and not numbers - just an example to differentiate), how can I do that? I tried duplicating the controltextdidchange but, as you can imagine, it didn’t work because I stuck at the way to refer the first to A1 textfield and the second to A2 textfield

Apart from this, I finished my first version of the script and it seems to work very nice thanks to your huge help :slight_smile: - otherwise I would not have been able to script it, this is an handy addition I would like to add to improve it

1.) You can use one or the other or both, but one reason for using both is that people can get creative when making errors. For example, using multiples of allowed characters that aren’t numbers. In that case, while the textField will contain valid characters, the string may not evaluate to a number. By using controlTextDidChange you can give an indication that a particular character was not accepted, while controlTextDidEndEditing can be used to check for a valid number after editing is completed. Putting up an error dialog for each unaccepted character would probably get annoying, so a better place would be controlTextDidEndEditing, where the check for a valid number can be performed after the whole thing is entered. After checking, you can also do stuff like clear the textField if there is an error. Placeholder text or a tooltip for the textField describing what it is for could also be added, for example:

A2_textField's setToolTip:"This text field only accepts the characters 0-9, comma, plus, minus, and `E`"

2.) I try to group the settings for a particular control together so things don’t get lost, but your location works fine.

3.) The notification sent to the delegate method (notificationObject in my example) contains an object property, which is the object that sent the notification. A comparison can be made to this object (the object variable in my example), but I should probably explain how to access the alternate control instances.

When the script sets up a group of alternate controls, it places their references in the controlGroups property by calling the addAlternate handler. The property is a dictionary that has keys for the group number (there is only one so far), within those there are dictionaries with keys for control values (for this script the popup value), and within those there is a key (controls) for a list of controls (the controls for a particular group and value) - you can also see the layout in Script Debugger’s variable listing for controlGroups. I use this arrangement because a valueForKeyPath method can be used to reduce the number of statements used to get individual items from the dictionary. For example, for the A2_textField, it is the third item of the list of controls that is at the dictionary’s keyPath of “1.This Thing.controls” (also see the list passed to the addAlternate handler).

With that said, for the controlTextDidEndEditing delegate handler you would rearrange things so that it looks something like:

# Verify the value in the formatted text field after editing is complete.
on controlTextDidEndEditing:notificationObject
    set object to notificationObject's object -- the textField that posted the notification
    set controlList to controlGroups's valueForKeyPath:"1.This Thing.controls" -- the controls to compare
    set theText to object's stringValue as text
    if object's isEqual:(item 1 of controlList) then -- check controls as desired
        -- do stuff for the A1_textField
    else if object's isEqual:(item 3 of controlList) then
        -- do stuff for the  A2_textField
    end if
end controlTextDidEndEditing:

The controlTextDidChange delegate handler can be arranged in the same way.

Using tab (to navigate the controls), return, or clicking in another control will end editing in the current textField and trigger its controlTextDidEndEditing delegate. I misspoke in my previous reply - the delegate handler will get called when exiting the dialog while still in the textField, but fixing any error may get more involved since the dialog will be in the process of being dismissed. One solution would be to keep the default button disabled while entering all the various fields, and only enable it after everything is properly filled in - you would need to keep track of all that, though.

You are perfectly right, I didn’t think about that possibility, because I was thinking only to numbers not in scientific notation, but a possible mistyping can be a double decimal sign

I tried completing the script but I can’t get nowhere else than putting a dialog error, which recognizes if the input is a number or not, but obviously makes the program stop :frowning:

This was my poor idea, but I’m sure it’s not what you had in mind

on controlTextDidEndEditing:notificationObject
	set object to notificationObject's object -- the textField that posted the notification
	set controlList to controlGroups's valueForKeyPath:"1.This Thing.controls" -- the controls to compare
	set theText to object's stringValue as text
	
	if object is equal to (item 1 of controlList) then -- check controls as desired
		-- do stuff for the A1_textField
	else if object is equal to (item 3 of controlList) then -- check for A2_textField
		try
			set theNumber to theText as number
		on error
			display dialog "Please enter a valid number for A2." buttons {"OK"} default button "OK"
		end try
	end if
end controlTextDidEndEditing:

This sounds nice but I don’t know if it’s complicated to implement

Your dialog in controlTextDidEndEditing works for me (although you aren’t doing anything to force a change, such as clearing the textField) - are you talking about not getting the error dialog if you close the dialog while still editing the textField? If so, which textFields must be complete (including the number fields that must have a valid number)?

My idea for the test code is that in A2_textfield only allowed characters can be written, and this is controlled by controlTextDidChange, with the setToolTip message when passing the cursor above the textfield, and I like the way this works.
I didn’t realize at first the meaning of the second handler, but you correctly taught me that some combinations of allowed characters that makes the input not a number anymore can be entered: in this case I would like to get a warning that something went wrong, but without having to start again from the beginning, and correct the mistyping.
In my poor solution, if I enter a string of allowed characters that are not a number (let’s say 1E2+) and I press OK, I get an alert but closing it the main dialog is gone. Furthermore, the string is kept. Obviously this happens because there are no instructions to make it behave differently, but this is because I simply don’t know how to write in a programming language what it has to do :slight_smile:

I’m always trying to work on this same example in the way that I can then try translate in my script, hoping that I can understand just a little of how it works :slight_smile:

The controlTextDidEndEditing handler is only called when one of the textFields (that has its delegate set) finishes editing - tabbing or clicking another control, etc. A return can also be used to end editing, except when the main dialog has a default button set, since the return will then be set to click the button (which will end the main dialog). Does it do the same thing if you don’t set a default button when creating the buttons?

You are right, I was unconsciously pressing return without exiting the textfield. So I got the warning but the dialog considered that I hit return and gave output regardless of the mistyping. But I fear that users that will use this script will not have the patience of clicking out of the textfield, so maybe this solution, as it is written, it’s not so useful. Apart from this, when using correctly (clicking out of the textfield and getting back to correct) the main dialog it’s heavily lagging and it’s not easy to correct the mistyped.

I attach the full script for an easier test, if you want to

use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use script "Dialog Toolkit Plus" version "1.1"
use script "Dialog Toolkit Plus Extended" version "1.1" -- datePicker


property popupItems : {"This Thing", "That Thing", "The Other Thing"} -- the operations to perform
global controlGroups -- this will be a dictionary of the alternate groups and their controls

# Check out additions to the number formatted text field.
on controlTextDidChange:notificationObject
	set object to notificationObject's object -- the textField that posted the notification
	set controlList to controlGroups's valueForKeyPath:"1.This Thing.controls" -- the controls to compare
	set theText to object's stringValue as text
	if object's isEqual:(item 1 of controlList) then -- check controls as desired
		set acceptable to characters of "ABC" -- or whatever
		set object to notificationObject's object -- the textField that posted the notification
		set theText to object's stringValue as text
		repeat with aCharacter in (characters of theText) -- just check everything in case something was pasted
			considering case -- for the exponent indicator `E`
				if aCharacter is not in acceptable then -- indicate and remove
					current application's NSBeep()
					set {prevTID, AppleScript's text item delimiters} to {AppleScript's text item delimiters, aCharacter}
					set {textItems, AppleScript's text item delimiters} to {text items of theText, ""}
					set {theText, AppleScript's text item delimiters} to {textItems as text, prevTID}
					(object's setStringValue:theText)
				end if
			end considering
		end repeat
	else if object's isEqual:(item 3 of controlList) then
		set acceptable to characters of "1234567890E,+- " -- or whatever
		set object to notificationObject's object -- the textField that posted the notification
		set theText to object's stringValue as text
		repeat with aCharacter in (characters of theText) -- just check everything in case something was pasted
			considering case -- for the exponent indicator `E`
				if aCharacter is not in acceptable then -- indicate and remove
					current application's NSBeep()
					set {prevTID, AppleScript's text item delimiters} to {AppleScript's text item delimiters, aCharacter}
					set {textItems, AppleScript's text item delimiters} to {text items of theText, ""}
					set {theText, AppleScript's text item delimiters} to {textItems as text, prevTID}
					(object's setStringValue:theText)
				end if
			end considering
		end repeat
		
	end if
end controlTextDidChange:


on controlTextDidEndEditing:notificationObject
	set object to notificationObject's object -- the textField that posted the notification
	set controlList to controlGroups's valueForKeyPath:"1.This Thing.controls" -- the controls to compare
	set theText to object's stringValue as text
	
	if object is equal to (item 1 of controlList) then -- check controls as desired
		-- do stuff for the A1_textField
	else if object is equal to (item 3 of controlList) then -- check for A2_textField
		try
			set theNumber to theText as number
		on error
			display dialog "Please enter a valid number for A2." buttons {"OK"} default button "OK"
		end try
	end if
end controlTextDidEndEditing:

on run -- example
	set controlGroups to current application's NSMutableDictionary's dictionary -- for using a keyPath
	set {viewWidth, theTop} to {400, 28}
	
	# controls are stacked from the bottom up for easier vertical positioning
	set {theButtons, minWidth} to create buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel" with equal widths
	if minWidth > viewWidth then set viewWidth to minWidth
	set {maxLabelWidth, controlLeft} to {100, 110}
	
	########## begin control group #1 - these are layered in the same space, with only the active alternate shown
	set newTop to theTop -- alternate A (This Thing)
	set {A2_textField, A2_labelField, newTop, fieldLeft} to create side labeled field ("") left inset 0 bottom (newTop) total width viewWidth label text "This #2:" field left controlLeft
	set {A1_textField, A1_labelField, newTop, fieldLeft} to create side labeled field ("") left inset 0 bottom (newTop + 6) total width viewWidth label text "This #1:" field left controlLeft
	repeat with anItem in (addAlternate(1, first item of popupItems, {A1_textField, A1_labelField, A2_textField, A2_labelField}))
		(anItem's setHidden:true)
	end repeat
	----
	set newTop to theTop -- alternate B (That Thing)
	set {B3_checkbox, newTop, newWidth} to create checkbox "That Checkbox" bottom (newTop - 24) max width (viewWidth / 2) left inset controlLeft without initial state
	set {B2_textField, B2_labelField, newTop, fieldLeft} to create side labeled field ("") left inset 0 bottom (newTop + 6) total width viewWidth label text "That #2:" field left controlLeft
	set {B1_textField, B1_labelField, newTop, fieldLeft} to create side labeled field ("") left inset 0 bottom (newTop + 6) total width viewWidth label text "That #1:" field left controlLeft
	repeat with anItem in (addAlternate(1, second item of popupItems, {B1_textField, B1_labelField, B2_textField, B2_labelField, B3_checkbox}))
		(anItem's setHidden:true)
	end repeat
	########## end control group #1
	
	set theTop to newTop -- continue from previous top
	set {divider, theTop} to create rule (theTop + 12) rule width viewWidth -- NSBox
	set {invoiceTextField, invoiceLabelField, theTop, fieldLeft} to create side labeled field ("") bottom (theTop + 12) total width (viewWidth / 2) label text "Invoice number:" field left controlLeft
	set {contractorTextField, contractorLabelField, theTop, fieldLeft} to create side labeled field ("") bottom (theTop + 12) total width viewWidth label text "Contractor:" field left controlLeft
	set {clientTextField, clientLabelField, theTop, fieldLeft} to create side labeled field ("") bottom (theTop + 12) total width viewWidth label text "Client:" field left controlLeft
	set {dateLabelField, theTop} to create label " Date:" left inset 60 bottom (theTop + 12) max width maxLabelWidth control size regular size
	set {datePicker, theTop} to create textual datepicker textual stepper picker elements YearMonthDay initial date (current date) min date seconds 0 max date seconds 0 left inset controlLeft bottom (theTop - 20) extra width 0 extra height 0
	
	set {operationPopup, theTop} to create popup popupItems left inset controlLeft bottom (theTop + 8) popup width 180 initial choice (last item of popupItems)
	operationPopup's setToolTip:"the operation to perform"
	operationPopup's setTarget:me
	operationPopup's setAction:"popupAction:" -- choose from group #1
	
	A1_textField's setDelegate:me
	A2_textField's setDelegate:me
	A1_textField's setToolTip:"This text field only accepts ABC"
	A2_textField's setToolTip:"This text field only accepts the characters 0-9, comma, plus, minus, and `E`"
	
	set allControls to {operationPopup, dateLabelField, datePicker, clientLabelField, clientTextField, contractorLabelField, contractorTextField, invoiceLabelField, invoiceTextField, divider, A1_labelField, A1_textField, A2_labelField, A2_textField, B1_labelField, B1_textField, B2_labelField, B2_textField, B3_checkbox} -- control references are listed from top to bottom for easier indexing
	set {buttonName, controlValues} to display extended window "Descriptive Title" acc view width viewWidth acc view height theTop acc view controls allControls buttons theButtons without align cancel button
	doStuff for (formatResult from controlValues)
end run

# Extract the desired controls from the returned dialog values and group into a record.
to formatResult from dialogResult
	tell (date (item 3 of dialogResult) as «class isot» as string) to set ISODate to text 9 thru 10 & "/" & text 6 thru 7 & "/" & text 1 thru 4 -- format date using the built-in ISO date class (no time zone info)
	tell dialogResult -- use record for consistent key:value pairs
		set common to {operation:item 1, theDate:ISODate, client:item 5, contractor:item 7, invoice:item 9}
		if first item is first item of popupItems then -- common + alternate A
			set results to common & {A1_textField:item 12, A2_textField:item 14}
		else if first item is second item of popupItems then -- common + alternate B
			set results to common & {B1_textField:item 16, B2_textField:item 18, B3_checkbox:item 19}
		else if first item is third item of popupItems then
			set results to common
		else -- oops
			set results to missing value
		end if
	end tell
	return results
end formatResult

# Add a set of alternate controls to the controlGroups dictionary - controls are returned for any customization.
to addAlternate((group as text), (keyName as text), (controls as list))
	if (controlGroups's valueForKey:group) is missing value then (controlGroups's setValue:(current application's NSMutableDictionary's dictionary()) forKey:group) -- add new group as needed
	controlGroups's setValue:(current application's NSDictionary's dictionaryWithDictionary:{controls:controls}) forKeyPath:(group & "." & keyName) -- add keyName alternate controls to group
	return controls
end addAlternate

# Action to change the visibility of group #1 controls based on the popup value.
on popupAction:sender
	repeat with aKey in popupItems -- hide everything
		set controlList to (controlGroups's valueForKeyPath:("1." & aKey & ".controls"))
		if controlList is not missing value then repeat with aControl in controlList
			((contents of aControl)'s setHidden:true)
		end repeat
	end repeat
	set controlList to (controlGroups's valueForKeyPath:("1." & ((sender's title) as text) & ".controls"))
	if controlList is not missing value then repeat with aControl in controlList -- show the selected control group
		((contents of aControl)'s setHidden:false)
	end repeat
end popupAction:

# Do stuff for the dialog result values.
to doStuff for dialogValues
	tell (operation of dialogValues) to if it is first item of popupItems then
		-- This
	else if it is second item of popupItems then
		-- That
	else if it is third item of popupItems then
		-- The Other
	end if
	return dialogValues
end doStuff