I have a dialog in a handler which asks for user input, checks the input and redisplays if user left something out. Trouble is because it’s recursive, the wrong value for the important variable comes out. This code illustrates the issue:
set theLabel to "You need to supply a location."
set theFinalLocation to getLocation(theLabel)
on getLocation(theLabel)
set theResult to (display dialog theLabel with title "Title" buttons {"Don't bother", "OK"} default answer "Location ?" default button 2)
set theLocation to text returned of theResult
if button returned of theResult is "OK" and (theLocation is "Location ?" or theLocation is "") then
display dialog "You forgot to supply a location." buttons {"OK"}
getLocation(theLabel)
end if
log theLocation
set theResult to text returned of theResult
return theResult
end getLocation
log theFinalLocation
If the user forgets to type the “location” they are reminded and the original dialog is redisplayed. After a few iterations the user gets it right. The processing of the iterations unwinds and if, say, there were three iterations theLocation ends up containing the original wrong value instead of the final correct value. That is unwinding of the script is in reverse order. So, the log might look like this:
(*London*)
(*Location ?*)
(*Location ?*)
(*Location ?*)
I’m sure there’s a principle I’m missing but I’m stuck and can’t find it. Is there a way to get the final, correct value for the theLocation variable ?
The issue is that the handler only sets ‘theLocation’ at the beginning of the handler and not when a valid location is actually entered.
If you want to stick with a recursive handler, then try adding an else to your if..then and set ‘theLocation’ there, like so:
set theLabel to "You need to supply a location."
set theFinalLocation to getLocation(theLabel)
on getLocation(theLabel)
set theResult to (display dialog theLabel with title "Title" buttons {"Don't bother", "OK"} default answer "Location ?" default button 2 cancel button 1)
set theLocation to text returned of theResult
if button returned of theResult is "OK" and (theLocation is "Location ?" or theLocation is "") then
display dialog "You forgot to supply a location." buttons {"OK"} default button 1
getLocation(theLabel)
else
set theLocation to text returned of theResult
return theLocation
end if
end getLocation
Thanks. My code is a model of the actual. I can’t change the basic structure. But you’ve made me think of another idea which is to declare theLocation as global. For a reason I don’t understand, it always contains the value I need. So, my script is structured like this:
global theLocation
set theLocation to ""
set theLabel to "You need to supply a location."
getLocation(theLabel)
on getLocation(theLabel)
set theResult to (display dialog theLabel with title "Title" buttons {"Don't bother", "OK"} default answer "Location ?" default button 2)
set theLocation to text returned of theResult
if button returned of theResult is "OK" and (theLocation is "Location ?" or theLocation is "") then
display dialog "You forgot to supply a location." buttons {"OK"}
getLocation(theLabel)
end if
set theResult to text returned of theResult
return theResult
end getLocation
log theLocation```
Cheers.
Is there a particular reason for using recursion? The reason for your success is that you are setting a variable that is outside the recursive loop, but note that each time you recursively call a handler, its entire state is saved on the stack so that each call level has everything it needs - just leaving it would leak that memory.
You can always display the dialog in a repeat loop that doesn’t exit until the result is acceptable, then you don’t need to worry about unwinding or leaking the stack.
Largely history. I’m too far down the road with this project to redesign it. I have some significant restructuring in the last 6 months but doubt there’s benefit in more. For reference, this project we discussed previously ("Stack overflow -2706" - Need help on logic flow). Haven’t seen a stack overflow since then.
The handler in question does a number of other things in response to a single dialog. The dialog is created with Dialog Toolkit Plus. There are a number of error checks although none have the difficulty I’ve encountered this time. When all checks are passed, the handler updates a plist file with 20 odd values and returns to another dialog.
I looked at using a display dialog inside a repeat loop instead of redisplaying the DTP dialog. That would have solved the recursion issue but would have made the UI uglier than it already is. There is a complication in that there are two boolean elements and a text field involved in the error checking.
I’ve got to the point that I’ll go with any change which works. I started this in 2017. In another life I would have waited until SwiftUI was available.
Cheers.
Neophyte has a solution, and red_menace’s post made me wonder how a repeat loop might work. I’ve included two possible approaches below.
I wasn’t sure what should happen when the user selects the Don’t Bother button, and the script included below cancels the operation. If the first dialog has a cancel button, it seems that the second dialog should also have one, so I added that.
set theLabel to "You need to supply a location."
set theFinalLocation to getLocation(theLabel)
on getLocation(theLabel)
repeat
--What should happen when the "Don't Bother" button is selected? The following cancels.
set theLocation to text returned of (display dialog theLabel with title "Title" buttons {"Don't Bother", "OK"} default answer "Location ?" cancel button 1 default button 2)
if (theLocation is "Location ?" or theLocation is "") then
--A cancel button was added to the following as a matter of personal preference.
display dialog "You forgot to supply a location." buttons {"Don't Bother", "Try Again"} cancel button 1 default button 2
else
return theLocation
end if
end repeat
end getLocation
If the dialog should not be cancelled when the Don’t Bother button is selected, I would probably just return something that can be dealt with in the main script. The following script returns the string User Declined.
set theLabel to "You need to supply a location."
set theFinalLocation to getLocation(theLabel)
on getLocation(theLabel)
repeat
set theResult to (display dialog theLabel with title "Title" buttons {"Don't Bother", "OK"} default answer "Location ?" default button 2)
set {theButton, theLocation} to {button returned, text returned} of theResult
if theButton is "Don't Bother" then
return "User Declined"
else if (theLocation is "Location ?" or theLocation is "") then
display dialog "You forgot to supply a location." buttons {"OK"} default button 1
else
return theLocation
end if
end repeat
end getLocation
The main thing would be to enclose the dialog and comparison(s) in a repeat statement, and instead of recursively calling the handler if whatever results are not acceptable, you test for when they are. My solution wouldn’t differ that much, it just exits the repeat instead - this also allows for multiple return values to be arranged as needed.
Yes. I’ll try for the next version of my applet.
Sorry, that was a quick and dirty model of the actual code. The UI in the applet tells the user about the problem and has a single “OK” button which takes them back to the dialog to correct the error or do something else.
Many thanks all. This discussion has given me new ways to think about the design of the logic in my applet.
Cheers.