cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
JMP is taking Discovery online, April 16 and 18. Register today and join us for interactive sessions featuring popular presentation topics, networking, and discussions with the experts.
Submit your abstract to the call for content for Discovery Summit Americas by April 23. Selected abstracts will be presented at Discovery Summit, Oct. 21- 24.
Craige_Hales
Super User
Modal Dialogs

The conversation

A dialogue is a conversation between two people. Two people usually have conversations like this:

  • Pilot: (putting sandwich back on the tray) "What do you think about the economy?"
  • Co-pilot: (not looking up from the phone) "I need a raise, what about you?"
  • Pilot: (wiping up garlic-bread crumbs) "Well, I'll be eating at the five-star this week."
  • Co-pilot: (frowning at the game) "Their chicken soup is tasty."

Those two people are exchanging information in a way that allows them to tend to other things during the conversation.

Sometimes two people switch to a conversational mode that becomes tightly focused:

  • Pilot: "We are running out of gas."
  • Co-pilot: "Switch to the other tank."
  • Pilot: "Did that an hour ago."
  • Co-pilot: "This is not good. We'll be over water for another hour."
  • Pilot: "I've got an idea, but you are not going to like it."

In this mode the co-pilot is now ignoring the Solitaire game and the pilot is ignoring the sandwich.

A dialog box is a window where a person has a conversation with a computer. JMP has two kinds of dialog boxes: modeless and modal. The find/replace dialog is modeless; it remains open and switches its focus between windows as easily as the pilot switched from the economy to food. JMP's save dialog is modal; once JMP enters the save-a-file mode, nothing else can get in the way until the file is saved or the save is canceled. JSL can create custom dialogs, modeless or modal, to give you some information and get an answer back.

Getting answers

The life of a modal dialog box:

  1. Build a display box tree
  2. Open a window to own and show the display box tree
  3. Wait for the window to close

Item 1, build the tree, is often done as the argument to the New Window function. Items 2 and 3 are the New Window function. Here's an alert box modal dialog.

rdata = New Window( title, <<modal, H List Box( Icon Box( icon ), Spacer Box( size( 10, 1 ) ), V Center Box( Text Box( text ) ) ) )

<<modal is the magic that makes this be a modal window that waits for the window to close before executing the next statement.

Just thought you should know...Just thought you should know...

This little window's two-way dialog is very simple:

  • JMP: "Wake up! This thing happened!"
  • You: "OK"

There is no data to return. The New Window function does return a value; it tells if you pressed the OK button 

{Button( 1 )}

or closed the window with the OS-provided close button 

{Button( -1 )}

It is returning a list with one element. You could test for Cancel vs OK using rdata["Button"] which will be -1 or 1. You probably don't care since you didn't put a CANCEL button in the dialog.

View more...
Not only do you need to supply the title and text, you need an icon. I like the addin for built in icons for a quick project like this. You can make custom bitmaps too, if you need them. @Justin_Chilton  Built-In JMP Icons 

The "Error" icon might not exist on the Mac.The "Error" icon might not exist on the Mac.
or a warning icon of a different size

warning!warning!

The trash

A long time ago JMP put the contents (display boxes) of a window into an internal structure called The trash before asking the OS to close the window. The trash evolved over time to solve many problems and sometimes create unintended side effects. One side effect was to make a modal dialog's display boxes available to JSL for a short time after the dialog was closed, and this was how some users retrieved answers from modal dialogs...literally getting them out of the trash.

  • Pilot: (tossing garlic-crumb napkin in the trash) "mmmm. good."
  • Co-pilot: (wrinkling nose) "I think I'll empty that now. Or maybe in a bit."

Problems with The trash revolve around how much it fills up and when to empty it. In early versions of JMP the trash was emptied when the next window was displayed, or some variation. When the trash is emptied, its contents are gone, and those modal dialog box variables vanish too. It mostly worked, most of the time, but it kept evolving, trying to solve various problems. Eventually @hecht_jmp  put a state-of-the-art solution into JMP that resolved the issues with the trash...but required a bit more of a plan for getting answers back from modal dialogs.

Getting answers the right way

If you want more data returned, you have to ask for it with <<return result:

rdata = New Window( "modal dialog test",
    <<modal,
    <<return result,
    answer = Text Edit Box( "enter your name" )
);

It said "Enter your name" before I typed over it.It said "Enter your name" before I typed over it.

{answer = "Craige", Button( 1 )}

You can use rdata["answer"] after the New Window() returns to get the value from the text box in the variable answer. (Do the cancel test first.)

Is a List like an Associative Array?

All of the examples above return a list. The items in the list all look like simple assignment statements or some kind of function call. A little used, poorly understood feature of JMP is subscripting lists using those names, rather than the more usual numeric index.

result = New Window( "Tell us about yourself",
    <<modal,
    <<return result,
    V List Box(
        Lineup Box( N Col( 2 ), spacing(20),
            Text Box( "name" ), name = Text Edit Box( "", <<SetWidth(200) ), 
            Text Box( "title" ), title = Text Edit Box( "", <<SetWidth(200) ) 
        ),
        H List Box( Button Box( "OK" ), Spacer Box( size( 10, 1 ) ), Button Box( "Cancel" ) )
    )
);

Bob is about to press OKBob is about to press OK

The value in result might be

{name = "Bob", title = "programmer", Button( 1 )}

or, if Bob cancels,

{name = "Bob", title = "programmer", Button( -1 )}

If( result["Button"] == 1,
    Write( Eval Insert( "\!n\[^result["name"]^ is a ^result["title"]^]\" ) )
, // else
    Write( "\!nthe dialog canceled." )
);

Bob is a programmer

The list does act a bit like an associative array, though it is done with a sequential lookup. (If you need an associative array, make one. Don't use this list behavior...it is a poor substitute.)

Modal dialog semantics

When your user presses the cancel button on a modal dialog, they expect nothing to be changed. Any JSL in a modal dialog should be operating the dialog without making changes to variables in the rest of your program. Here's a bigger example; it has a modeless dialog for launching reports:

You can work in other windows while this window is open; it is a modeless dialog.You can work in other windows while this window is open; it is a modeless dialog.

Each time you press the Make Report button, another window opens:

A report showing the values from the modeless launcher.A report showing the values from the modeless launcher.

There is also a Set Parameters button that opens a modal dialog with a validation routine. This cancel button will not apply any changes to the launcher above.

The validation routine opens yet another modal dialog.The validation routine opens yet another modal dialog.

  • Pilot: (squinting) "This looks complicated!"
  • Co-pilot: "We can do this. It has comments!"
// here's a simple modal window. It provides a warning, but no data is returned.
alert = Function( {title, text, icon = "error"},
	New Window( title, <<modal, H List Box( Icon Box( icon ), Spacer Box( size( 10, 1 ) ), V Center Box( Text Box( text ) ) ) )
);
// alert( "Clock says", "time is running out!", "Warning" ) // the error icon (default) looks better

testNames = {"apples", "bananas", "candy"}; // names of the radio buttons

testIndex = 3; // selected radio button is "candy"
numberOfRuns = 10; // value for Number Edit

// this function opens a modal dialog with current values and validates them and sets the testIndex and numberOfRuns
chooseNamesAndRuns = Function( {title, lo = 5, hi = 10}, // parms for validation
	{ex}, // local variable
	ex = New Window( title,
		<<modal, // modal means JMP must wait for an answer
		<<On Validate( // the window wants to close; validate settings
			If( lo <= (numberOfRunsResult << get) <= hi, // validation
				1; // all good, return 1 to dismiss window
			, // else something is wrong
				alert( "Usage", Eval Insert( "use ^lo^ <= numruns <= ^hi^" ) ); //
				0; // return 0 to keep window open
			)
		),
		<<Return Result, // the keyword that makes the modal window return values from the displaybox tree
		V List Box(
			H List Box(
				V List Box( Text Box( "Test Set" ), testIndexResult = Radio Box( testNames, <<set( testIndex ) ) ), // return testIndexResult
				Spacer Box( size( 20, 1 ) ),
				V List Box( Text Box( "Number of Runs" ), numberOfRunsResult = Number Edit Box( numberOfRuns ) ) // return numberOfRunsResult
			) //
		, //
			H List Box(
				Button Box( "OK" ), //
				Button Box( "Cancel" ) //
			) //
		)
	);
    // because of modal, the window must be dismissed with OK or CANCEL before the next statement is executed.
	// now, ex is a list:
	//    {testIndexResult = 1, numberOfRunsResult = 43, Button( 1 /*OK*/)}
	//    {testIndexResult = 2, numberOfRunsResult = 44, Button( -1 /*CANCEL*/)}
	If( ex["button"] == 1, // 1 is for the OK button, -1 is CANCEL. If OK, copy values back...
		numberOfRuns = ex["numberOfRunsResult"]; // only update the application's numberOfRuns and testIndex
		testIndex = ex["testIndexResult"]; //       if OK is pressed. CANCEL does nothing.
	);
);


// this modeless dialog sets up reports. It has a button to 
// open a modal parameter dialog and a button to make reports.
app = New Window( "our application", 
	V List Box(
		Panel Box( "setup the report",
			H List Box(
				V List Box( // stack these to the left of the button
					H List Box( Text Box( "name=" ), nb = Text Box( "" ) ),
					H List Box( Text Box( "runs=" ), nr = Text Box( "" ) )
				),
				Spacer Box( size( 10, 1 ) ),
				V Center Box(
					Button Box( "Set Parameters", // launch a modal dialog
						chooseNamesAndRuns( "Choose test case", 4 ); // title, min=4, max is default, see function
						syncNames(); // re-sync variables that changed into the window
					)
				)
			),
			<<margin( 9 ) // add space around the "setup" panel
		),
		H List Box(
			Button Box( "Make Report", makereport() ) //
		,
			Button Box( "Close", app << closewindow ) // modeless dialogs don't have OK/Cancel; here's one way to close
		)
	)
);

// this function synchronizes our applications display with the current data 
syncNames = Function( {},
	nb << settext( testNames[testIndex] ); // nb is the name text box
	nr << settext( Char( numberOfRuns ) ); // nr is the number of runs text box
);
syncNames(); // initial display. Note the values were set to "" in new window() and need an initial sync.

// a minimal placeholder; open a new window containing a report built from parameters set above
makereport = Function( {},
	New Window( "Marketing Report",
		V List Box(
			Spacer Box( size( 500, 1 ) ),
			Text Box( testNames[testIndex], <<Font Color( "red" ), <<Set Font Size( 40 ) ),
			Text Box( Char( numberOfRuns ), <<Font Color( "green" ), <<Set Font Size( 20 ) )
		)
	)
);
  • Pilot: "Thanks!"
Last Modified: Nov 15, 2021 6:56 AM