cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
Browse apps to extend the software in the new JMP Marketplace
Choose Language Hide Translation Bar
SDF1
Super User

JSL Help: Set Function() command syntax help

Hi All,

 

  Back with another JSL programming question. It's related to the other posts through, e.g. here.

 

  I would like to have a Check Box() functionality that when the check box is checked (state=1), the selected rows (it happens to be rows where RSquare is negative) are selected and then hidden and removed so the updated Graph builder shows the desired output. Then, if the box is unchecked (state=0), in unhides and unexcludes those rows. The Set Function() call I'm using is below, and it works if I run the "script" of the Set Function() manually, but it doesn't do it automatically. I can't figure out what I'm missing in order for it to update automatically. I found a neat post here, and I'm trying to do something like what this person did, but for graph builder update.

JSL Set Function call:

 cb << Set Function(
{this},
this = cb << Get;
If(
this == 1,
dt_results << Select Rows( neg_rows ) << Hide and Exclude;
dt_results << Clear Select;
dt_tb = dt_results << Get As Report();,
this == 0,
dt_results << Select Rows( neg_rows ) << Unhide;
dt_results << Select Rows( neg_rows ) << Unexclude;
dt_results << Clear Select;
dt_tb = dt_results << Get As Report()
);
);cb << Set Function( {this}, this = cb << Get; If( this == 1, dt_results << Select Rows( neg_rows ) << Hide and Exclude; dt_results << Clear Select;, this == 0, dt_results << Select Rows( neg_rows ) << Unhide; dt_results << Select Rows( neg_rows ) << Unexclude; dt_results << Clear Select; ); );

Where cb is referred to earlier in the code as:

cb = Check Box( "Remove bad points (*'s)?", <<Set( 0 ) )

Starting output, or whenever check box is state = 0.:

DiedrichSchmidt_0-1636404934662.png

Desired output whenever check box is state = 1:

DiedrichSchmidt_1-1636404999029.png

It would be nice to also update the data table as Report on the right so that any of the rows that were excluded were then removed from the Report view on the right, but I think that might be much harder than it sounds. I tried adding an additional Get As Report update in the Set Function() command, but it does nothing.

 

Any feedback is appreciated, thanks!,

DS

  

9 REPLIES 9
pmroz
Super User

Re: JSL Help: Set Function() command syntax help

Try something like this:

nw = new window("Test",
	one_cb = checkbox({"Turn On"},
		one_value = one_cb << Get;
		If (one_value,
			dt_results << Select Rows( neg_rows ) << Hide and Exclude;
			dt_results << Clear Select;
			dt_tb = dt_results << Get As Report();
// else
		,
			dt_results << Select Rows( neg_rows ) << Unhide;
			dt_results << Select Rows( neg_rows ) << Unexclude;
			dt_results << Clear Select;
			dt_tb = dt_results << Get As Report()
		);
	)
);
SDF1
Super User

Re: JSL Help: Set Function() command syntax help

Hi @pmroz ,

 

  Thanks for the feedback. I was looking to just update the current window and not create a new one. I have been able to get things to work for the graph so that it updates on the check/uncheck state of the Check Box(). Unfortunately, I still can't get the table box that contains the data table as a report to update as I would like. This part is turning out to be much more complicated than I would have thought, but it's really not all that important. Better to have the graphical display work as expected. Still, it would be nice if the table box could be updated.

 

Thanks!,

DS

ErraticAttack
Level VI

Re: JSL Help: Set Function() command syntax help

Note that you cannot rely on JSL scope walking being consistent on <<Set Function() functions.  That is, when the function run you should _always_ assume that it has no visibility to any variables defined outside of that function.  It clearly can work sometimes, it's just not reliable.  Best practice is to create a namespace that holds any relevant variables to your script and pass that namespace (by reference) into your <<Set Function() function.  This can be easily done with an Eval( Eval Expr( ... ns = Namespace( Expr( ns << Get Name ) ) ) type of statement.

 

Mostly it is that JSL does not have rigorous scope control as, for instance, Python would have.  For any type of complicated interactive UI you should always explicitly scope all variables that need to persist across multiple display element callbacks.

 

Also, as a point of both aesthetic and practical code format, you should always put commas at the level of the enclosing fence, as below.  This allows for very quick and easy visual understanding of the blocks of code.  Notice the easy visual indication of where the different blocks of the IF statement are.

 

As a final note -- you should link the closing of your UI window with the deletion of the namespace by utilizing the <<OnClose() method of the NewWindow() object.

 

ns = <relevant namespace with variables defined above>

Eval( Eval Expr(
ns:cb << Set Function( {this},
	ns = Namespace( Expr( ns << Get Name ) );
	state = this << Get;
	If(
		state == 1,
			ns:dt_results << Select Rows( ns:neg_rows ) << Hide and Exclude;
			ns:dt_results << Clear Select;
	,
		state == 0,
			ns:dt_results << Select Rows( ns:neg_rows ) << Unhide;
			ns:dt_results << Select Rows( ns:neg_rows ) << Unexclude;
			ns:dt_results << Clear Select;
	);
);
) );
Jordan
ih
Super User (Alumni) ih
Super User (Alumni)

Re: JSL Help: Set Function() command syntax help

Hi  @ErraticAttack,

 

From my experience, variable scoping is consistent. It may not be intuitive, but I've always found it to be predictable.  If you are running into inconsistencies it might be worth digging into the scripting guide and try cross-check how different symbols are scoped.

 

For some situations, I've had great success using the calling context functions to simplify function calls, normally for exploratory analysis or scripts that are run interactively. For example a log function might send messages to a file or window and include iterator or step values without having to type them out/copy/paste them every time.

 

Names default to here(1);

lg = function({msg},
	write(msg || try(", i=" || char(i), "") || "\!N")
);

lg("starting loop");
local({i},for(i=1, i<= 5, i++, lg("done")))
SDF1
Super User

Re: JSL Help: Set Function() command syntax help

Hi @ErraticAttack ,

 

  Thanks for your feedback. I have noticed that this is the second time you've recommended the Namespace() to me ;-).

 

  As a result, I have spent the past day and a half reading up on using NameSpace in my scripting guide, which is better than the Scripting Index, but still doesn't really give the best examples like how you are using it.  Do you have any primer that is better than the JMP 14 Scripting Guide or Scripting Index on how to use Namespace in the context of how you are using it?

 

  I have to admit I don't quite follow what you're doing and need to learn it better. However, based on your post here, and on the example code you provided on one of my other posts here,  I was able to re-write my section of script that calls the first window using the Namespace(). But, I'm still not quite sure how I would script-in the subsequent windows and displays that I am able to get to work via my not-so-great scripting skills. I think it's going to take some time to learn how to use Namespace correctly before I can switch over to that programmatic method, which I would like to do.

 

  I was able to get the graph builder of the display to update upon checking/unchecking the box to hide/exclude the data that is negative (in this case meaningless). The script below does what I want (except for updating the table box when certain data is removed from the graph), but I understand that it's not ideal from the standpoint of programming.

 

Thanks!,

DS

  

Names Default To Here( 1 );

lbWidth = 168;

dt = Open( "$SAMPLE_DATA/Boston Housing.jmp" );

// Expression to store the current settings in global variables
recallRolesS1 = Expr(
	::dtchosenRecall = dtchosen
);

// Expression to clear all current settings. KW: Clear
clearRoles = Expr(
	Try(
		colListY << RemoveAll;
		ColListX << RemoveAll;
		ColListW << RemoveAll;
		ColListF << RemoveAll;
		colListV << RemoveAll;
	)
);

// Expression to store the current settings in global variables
recallRoles = Expr(
	::ycolRecall = colListY << GetItems;
	::xcolRecall = colListX << GetItems;
	::WcolRecall = colListW << GetItems;
	::FcolRecall = colListF << GetItems;
	::vcolRecall = colListV << GetItems;
	::max_spRecall = max_sp_input << get;
	::partVPRecall = partVP_input << Get;
);

//Function to choose the data table to model. KW: choose_data
choose_data_table = Function( {},
	list = {};
	For( i = 1, i <= N Table(), i++,
		list[i] = Data Table( i ) << get name
	);
	win = New Window( "Select a data table",
		<<Modal,
		hb = H List Box(
			Panel Box( "Choose a data table", dt = List Box( list, max selected( 1 ), dtchosen = Data Table( (dt << get selected)[1] ) ) ), 

		)
	);
);

Part_rerun = Expr(
	
	Close( dt_results, No Save );
	
	NPCs_num = NPCs;
	
	str = Eval Insert(
		"report = (dtchosen<<Partition(
				Y(Eval(ycols)),
				X(Eval(xcols)),
				Weight(Eval(wcols)),
				Freq(Eval(Fcols)),
				Validation(Eval(vcols)),
				Informative Missing(1),
				Split Best(^NPCs_num^),
				Validation Portion(^partVP^),
				Split History(1)
				)
				
			)<<Report;"
	);
	Eval( Parse( str ) );
	
);

Part_TB = Expr(

	Part_TBWin = New Window( "Select the Best split to re-run",
		<<Return Result,
		<<On Validate,
		Outline Box( "Partitioning Results (Select the best to re-run))",
			H List Box(
				V List Box(
					If(
						N Items( vcols ) == 1 | (N Items( vcols ) == 0 & Is Missing( PartVP ) == 0),
							part_gb = Graph Builder(
								Size( 579, 417 ),
								Show Control Panel( 0 ),
								Variables(
									X( :Splits ),
									Y( :Training RSquare ),
									Y( :Validation RSquare, Position( 1 ) ),
									Y( :RSquare Diff ),
									Y( :Penalized Valid RSquare, Position( 2 ) )
								),
								Elements( Position( 1, 1 ), Line( X, Y( 1 ), Y( 2 ), Legend( 12 ) ), Points( X, Y( 1 ), Y( 2 ), Legend( 14 ) ) ),
								Elements( Position( 1, 2 ), Line( X, Y( 1 ), Y( 2 ), Legend( 13 ) ), Points( X, Y( 1 ), Y( 2 ), Legend( 15 ) ) ),
								SendToReport(
									Dispatch(
										{},
										"graph title",
										TextEditBox,
										{Set Text( "Training & Validation RSquare / RSquare Diff & Penalized Valid RSquare vs. Splits" )}
									)
								)
							),
						N Items( vcols ) == 0 & Is Missing( PartVP ) == 1,
							part_gb = Graph Builder(
								Size( 531, 456 ),
								Show Control Panel( 0 ),
								Variables( X( :Splits ), Y( :Training RSquare ), Y( :Penalized Train RSquare ) ),
								Elements( Position( 1, 1 ), Line( X, Y, Legend( 7 ) ), Points( X, Y, Legend( 9 ) ) ),
								Elements( Position( 1, 2 ), Line( X, Y, Legend( 8 ) ), Points( X, Y, Legend( 11 ) ) )
							)
					),
					V List Box(
						Text Box(
							"Warning: Data where marker is '*' have a negative R² or Validation R² > Training R².",
							<<Set Font Size( 12 ),
							<<Set Width( 600 )
						),
						If(
							N Items( neg_rows ) != 0,
								cb = Check Box(
									"Remove bad points from graph (*'s)?",
									<<Set( 0 ),
									<<Set Function(
										Function( {},
											state = cb << Get;
											If(
												state == 0,
													dt_results << Select Rows( neg_rows ) << Unhide;
													dt_results << Select Rows( neg_rows ) << Unexclude;
													dt_results << Clear Select;
													dt_tb=dt_results<<Get As Report;
													Part_TBWin<<Update Window,
												state == 1,
													dt_results << Select Rows( neg_rows ) << Hide and Exclude;
													dt_results << Clear Select;
											);
										)
									)
								),
							N Items( neg_rows ) == 0, cb = Text Box( "" )
						), 

					)
				), 
				dt_tb = dt_results << Get As Report(),
				Panel Box( "Action",
					Lineup Box( N Col( 1 ),
						Text Box( "Re-run Partion", <<Justify Text( "Center" ) ),
						Button Box( "OK",
							Part_rerun;
							Part_TBWin << Close Window;
						),
						Button Box( "Cancel",
							Part_TBWin << Close Window;
							Try( Close( dt_results, No Save ) );
						),
						Spacer Box( Size( 0, 15 ) ),
						Button Box( "Relaunch",
							Part_TBWin << Close Window;
							Try( Close( dt_results, No Save ) );
							Firstwin;
						),
						Spacer Box( Size( 0, 8 ) ),
						Button Box( "Help", Web( "https://www.jmp.com/en_ch/support/online-help-search.html?q=*%3A*" ) )
					)
				)
			)
		)
	);
	

	
	tb = dt_tb[Table Box( 1 )];
	For( ri = 1, ri <= N Col( dt_results ) - 2, ri++,
		dt_tb[Table Box( 1 )][Number Col Box( ri + 1 )] << Set Format( "Fixed Dec", 6, 4 )
	);
	
	tb << Set Shade Headings( 1 );
	tb << Set Heading Column Borders( 1 );
	tb << Set Selectable Rows( 1 );
	tb << Set Click Sort( 1 );
	tb << Set Row Change Function(
		Function( {this},
			{SelRows},
			SelRows = this << Get Selected Rows;
			If(
				N Items( SelRows ) > 1, Throw( "Too many items selected!\!rOnly one selection possible." ),
				N Items( SelRows ) == 1,
					SelRows = this << Get Selected Rows;
					NPCs = SelRows[1];
			);
			dt_results << Clear Select;
			dt_results << Select rows( SelRows );
		)
	);
		
);


Part = Expr(
	If(
		N Items( vcols ) == 1,
			dt_results = New Table( "Partition_Results",
				Add Rows( max_sp ),
				New Column( "Splits", Numeric, Continuous ),
				New Column( "Training RSquare", Numeric, Continuous ),
				New Column( "Validation RSquare", Numeric, Continuous ),
				New Column( "Training RASE", Numeric, Continuous ),
				New Column( "Validation RASE", Numeric, Continuous ),
				New Column( "RSquare Diff", Numeric, Continuous, Formula( "Training RSquare"n - "Validation RSquare"n ) ),
				New Column( "Penalized Valid RSquare", Numeric, Continuous, Formula( "Training RSquare"n - 0.05 * "Splits"n ) ),
				New Column( "Training N", Numeric, Continuous ),
				New Column( "Validation N", Numeric, Continuous )
			),
		N Items( vcols ) == 0 & Is Missing( partVP ) == 0,
			dt_results = New Table( "Partition_Results",
				Add Rows( max_sp ),
				New Column( "Splits", Numeric, Continuous ),
				New Column( "Training RSquare", Numeric, Continuous ),
				New Column( "Validation RSquare", Numeric, Continuous ),
				New Column( "Training RASE", Numeric, Continuous ),
				New Column( "Validation RASE", Numeric, Continuous ),
				New Column( "RSquare Diff", Numeric, Continuous, Formula( "Training RSquare"n - "Validation RSquare"n ) ),
				New Column( "Penalized Valid RSquare", Numeric, Continuous, Formula( "Validation RSquare"n - 0.05 * "Splits"n ) ),
				New Column( "Training N", Numeric, Continuous ),
				New Column( "Validation N", Numeric, Continuous )
			),
		N Items( vcols ) == 0,
			dt_results = New Table( "Partition_Results",
				Add Rows( max_sp ),
				New Column( "Splits", Numeric, Continuous ),
				New Column( "Training RSquare", Numeric, Continuous ),
				New Column( "Training RASE", Numeric, Continuous ),
				New Column( "Penalized Train RSquare", Numeric, Continuous, Formula( "Training RSquare"n - 0.05 * "Splits"n ) ),
				New Column( "Training N", Numeric, Continuous )
			)
	);
	
	For( i = 1, i <= max_sp, i++,
		dt_results:"Splits"n[i] = i
	);
	
	dt_name = dtchosen << Get Name;
	
	For( i = 1, i <= N Rows( dt_results ), i++,
		Psplit = dt_results:"Splits"n[i];
		str = Eval Insert(
			"report = (dtchosen<<Partition(
				Y(Eval(ycols)),
				X(Eval(xcols)),
				Weight(Eval(wcols)),
				Freq(Eval(Fcols)),
				Validation(Eval(vcols)),
				Informative Missing(1),
				Split Best(^Psplit^),
				Validation Portion(^partVP^),
				Invisible
				)
				
			)<<Report;"
		);
		Eval( Parse( str ) );
		
		partWinName = Report << Get Window Title;
		
		w = Window( partWinName );
		RSq_mat = w[Outline Box( "Partition for " || ycols[1] )][Table Box( 1 )] << Get As matrix;
		
		If(
			N Items( vcols ) == 1,
				dt_results:"Training RSquare"n[i] = RSq_mat[1];
				dt_results:"Training RASE"n[i] = RSq_mat[2];
				dt_results:"Training N"n[i] = RSq_mat[3];
				dt_results:"Validation RSquare"n[i] = RSq_mat[2, 1];
				dt_results:"Validation RASE"n[i] = RSq_mat[2, 2];
				dt_results:"Validation N"n[i] = RSq_mat[2, 3];,
			N Items( vcols ) == 0 & Is Missing( PartVP ) == 0,
				dt_results:"Training RSquare"n[i] = RSq_mat[1];
				dt_results:"Training RASE"n[i] = RSq_mat[2];
				dt_results:"Training N"n[i] = RSq_mat[3];
				dt_results:"Validation RSquare"n[i] = RSq_mat[2, 1];
				dt_results:"Validation RASE"n[i] = RSq_mat[2, 2];
				dt_results:"Validation N"n[i] = RSq_mat[2, 3];,
			N Items( vcols ) == 0,
				dt_results:"Training RSquare"n[i] = RSq_mat[1];
				dt_results:"Training RASE"n[i] = RSq_mat[2];
				dt_results:"Training N"n[i] = RSq_mat[3];
		);
		Report << CloseWindow;
	);
	Try(
		neg_rows = dt_results << Get Rows Where( :"RSquare Diff"n < 0 | :"Training RSquare"n < 0 | :"Validation RSquare"n < 0 );
		dt_results << Select Rows( neg_rows ) << Markers( 11 );
		dt_results << Clear Select;
	);
	
	Part_TB;
);

AutoPart = Expr(
	Partwin = New Window( "Select Columns",
		<<Return Result,
		<<On Validate,
		Border Box( Left( 3 ), Top( 2 ),
			Outline Box( "JSL to help with Partitioning",
				<<Set Font Size( 12 ),
				V List Box(
					H List Box(
						Panel Box( "Select Columns",
							V List Box( colListData = Col List Box( dtchosen, All, Grouped, width( lbWidth ), nLines( 12 ) ) ),
							Spacer Box( Size( 0, 16 ) )
						),
						Panel Box( "Cast Selected Columns into Roles",
							Lineup Box( N Col( 2 ), Spacing( 3, 2 ),
								Button Box( "Y, Response", colListY << Append( colListData << GetSelected ) ),
								colListY = Col List Box( width( lbWidth ), nLines( 4 ), Min Items( 1 ) ),
								Button Box( "X, Factor", colListX << Append( colListData << GetSelected ) ),
								colListX = Col List Box( width( lbWidth ), nLines( 4 ), Min Items( 1 ) ),
								Button Box( "Weight", colListW << Append( colListData << GetSelected ) ),
								colListW = Col List Box(
									width( lbWidth ),
									Max Selected( 1 ),
									Max Items( 1 ),
									nLines( 1 ),
									<<Set Data Type( "Numeric" )
								),
								Button Box( "Freq", colListF << Append( colListData << GetSelected ) ),
								colListF = Col List Box(
									width( lbWidth ),
									Max Selected( 1 ),
									Max Items( 1 ),
									nLines( 1 ),
									<<Set Data Type( "Numeric" )
								),
								Button Box( "Validation", colListV << Append( colListData << GetSelected ) ),
								colListV = Col List Box(
									width( lbWidth ),
									nLines( 1 ),
									Max Selected( 1 ),
									Max Items( 1 ),
									<<Set Data Type( "Numeric" ), 

								)
							)
						),
						Panel Box( "Action",
							Lineup Box( N Col( 1 ),
								Button Box( "OK",
									recallRoles;
									max_sp = max_sp_input << get;
									partVP = partVP_input << Get;
									ycols = ColListY << Get Items;
									xcols = ColListX << Get Items;
									Wcols = ColListX << Get Items;
									Fcols = ColListF << Get Items;
									vcols = ColListV << Get items;
									Part;
									Partwin << Close Window;
								),
								Button Box( "Cancel", Partwin << Close Window ),
								Spacer Box( Size( 0, 22 ) ),
								Button Box( "Remove",
									colListY << RemoveSelected;
									colListX << RemoveSelected;
									colListW << RemoveSelected;
									colListF << RemoveSelected;
									colListV << RemoveSelected;
								),
								Button Box( "Recall",
									clearRoles;
									Try(
										colListY << Append( ::ycolRecall );
										colListX << Append( ::xcolRecall );
										colListW << Append( ::WcolRecall );
										colListF << Append( ::FcolRecall );
										colListV << Append( ::vcolRecall );
										max_sp_input << Set( ::max_spRecall );
										partVP_input << Set( ::partVPRecall );
									);
								),
								Button Box( "Relaunch",
									FirstWin;
									Try( Close( dt_results, No Save ) );
									Partwin << Close Window;
								),
								Spacer Box( Size( 0, 22 ) ),
								Button Box( "Help", Web( "https://www.jmp.com/en_ch/support/online-help-search.html?q=*%3A*" ) )
							)
						)
					),
					H List Box(
						Panel Box( "Max partition splits", max_sp_input = Number Edit Box( 20, 6 ) ),
						Panel Box( "Validation Portion (if no validation column)", partVP_input = Number Edit Box( ., 6 ) )
					)
				)
			)
		)
	)
);

//Interactive dialogue window to start Generalized Tuning
FirstWin = Expr(
	AutoTuneDlg1 = New Window( "Partioning Automation",
		<<Return Result,
		<<On Validate,
		Border Box( Left( 3 ), top( 2 ),
			Outline Box( "Something to help simplify partitioning",
				<<Set Font Size( 12 ),
				H List Box(
					V List Box(
						Panel Box( "Select Data Table",
							H List Box( Button Box( "Select Data Table", choose_data_table ), Spacer Box( Size( 100, 0 ) ) )
						), 

					),
					Panel Box( "Action",
						Lineup Box( N Col( 1 ),
							Button Box( "OK",
								recallRolesS1;
								AutoPart;
								AutoTuneDlg1 << Close Window;
							),
							Button Box( "Cancel", AutoTuneDlg1 << Close Window ),
							Spacer Box( Size( 0, 25 ) ),
							Button Box( "Recall", Try( dtchosen = ::dtchosenRecall ) ),
							Button Box( "Help", Web( "https://www.jmp.com/en_ch/support/online-help-search.html?q=*%3A*" ) )
						)
					)
				)
			)
		)
	)
);

FirstWin;
ErraticAttack
Level VI

Re: JSL Help: Set Function() command syntax help

@ih, as an example of JMP not doing scoping as one might expect (especially coming from Python for example), simply launch two windows from this script -- the names only link to the second window, not to the first.  This is the biggest problem I've found that people have with scoping (but not the only problem).  JMP provides some convenient pseudo-namespaces, such as the window:<var> namespace.  However, these aren't proper namespace variables and don't work fully as expected in large scripts.

 

Names Default to Here( 1 );

New Window( "TEST",
	V List box(
		teb = Text Edit Box( "", <<Set Hint( "Type Something" ),
			<<Set Function(
				Function( {this},
					tb << Set Text( "You typed: " || (this << Get Text) )
				)
			)
		)
	,
		tb = Text Box()
	)
)

 

 

@SDF1, for the explicit namespace scoping what I've done is create a library which does all of the tedious scope injection stuff via meta-programming.  This has the added benefit of being able to define classes almost exactly as they work in Python (minus the convenient lookup overrides).  I cannot share the library as it is part of my company work, but this is essentially what is happening:

 

Say you have a simple script such as this

 

Names Default to Here( 1 );

/*
	Doing something initialization here!!
*/
dt = Current Data Table();

New Window( "Super Useful Script",
,
	<<On Close(
		/*
			would like some cleanup -- such as closing any temp tables
		*/
		1
	)
,
	V List Box(
		Button Box( "Useful 1-click function",
			<<Set Function(
				Function( {this},
					Print( "Done" )
				)
			)
		)
	,
		H List Box(
			Text Edit Box( "", <<Set Hint( "Enter Value" ),
				<<Set Function(
					Function( {this},
						Print( "Doing something special" )
					)
				)
			)
		)
	)
)

This script has two interactive UI elements, some initialization, and potentially some cleanup on window close.  Note that conceptually the display tree and logic are intermixed and there are very deep blocks.  Code looks better (and is easier to maintain) when conceptually distinct elements are distinct within the code as well.  It would be nice to simply create a button box with a title and a function name to call.  The function that it calls will then be a top-level function (that can be easily called otherwise by any part of the script) and will be separate from the display tree.

 

You can transform this into something _much_ easier to digest and work with that looks like the following:

 

 

/* super useful script */

initialize = Function( {}, // entry point to script
	/* perform any initialization needed here */
	self:dt = Current Data Table();
	1
);

content = Function( {}, // this holds the display tree
	V List Box(
		self:one click button = meta:Button Box( "Useful 1-click function", "call one click" )
	,
		H List Box(
			self:value teb = meta:Text Edit Box( "", "handle value change" ) << Set Hint( "Enter Value" )
		)
	)
);

finalize = Function( {}, // called after display tree has finalized
	1
);

on close = Function( {}, // called when window wants to close
	1
);

call one click = Function( {}, // called when button is pressed (line 11)
	Print( "Done" )
);

handle value change = Function( {}, // called when TEB value changes(line 14)
	Print( "Doing something special" )
);

Now clearly this script wont do much if you press the normal JSL run script button.  What you need to do with this is to first define the meta namespace (which holds the ButtonBox and TextBox wrapper so that they can link nicely with the rest of the script) and define a meta-programming wrapper function that will do the following:

 

 

Take a prototype script such as this

 

<some script is above me>

do someting = Function( {},
	...
);

finalize = Function( {},
	...
);

<more script is below me>

Transform it to this prototype:

 

 

<some script is above me>

self:do someting = Function( {},
	self = Namespace( "#434" );
	...
);

self:finalize = Function( {},
	self = Namespace( "#434" );
	...
);

<more script is below me>

 

 

This meta-programming function will define `self` as simply `self = New Namespace()` and then inject the self definition into each function as above (the "#434" is of course going to be different each time).   After the script has been rewritten, simply do the following (this is taken almost exactly from my library)

 

If( self << Contains( "initialize" ), Try( self:initialize(), Print( "Error with self:initialize" ); Show( exception_msg ); ); );
Eval(
	Substitute(
		Expr(
			self:window = New Window( self:tool name,
				<<On Close(
					self = Namespace( _self_ );
					_window_return_value_ = 1;
					Try(
						_window_return_value_ = self:close
					);
					If( Not( Is Number( _window_return_value_ ) ) | Is Missing( _window_return_value_ )  | _window_return_value_ != 0,
						self:destroy;
						node namespace:clear graveyard;
					);
					If( _window_return_value_ != 0,
						1
					,
						_window_return_value_
					)
				)
			,
				V List Box(
					Text Box( _self_, <<Visibility( "Collapse" ) )
				,
					self:main box = V List Box(
						self:container = self:content
					)
				)
			)
		)
	,
		Expr( _self_ ), self << Get Name
	)
);
Try(
	self:window << Inval << Update Window;
	Try( self:window << Bring Window to Front() );
);
Try( self:window << Set Window Icon( "<<put your icon here>>" ) );

Try( 
	self:finalize
,
	Print( "Error with self:finalize" );
	Show( exception_msg );
);

 

 

Here is an actual example I have in my script.  The input before meta-programming is this:

 

tool name = "Example";

initialize = Function( {},
	{Default Local},
	Show( "initialized" )
);

content = Function( {},
	H List Box(
		meta:Button Box( "Hello World", "run" )
	,
		self:n days box = meta:Number Edit Box( 30, "run" )
	)
);

run = Function( {},
	{var1, var2},
	Show( "running" )
);

finalize = Function( {},
	Show( "finalized" );
	show( self:n days box << Get );
);

0;

just before evaluating the script (after the meta-programming step) it looks like this

self:tool name = "Example";
self:initialize = Function( {},
	{Default Local},
	self = Namespace( "#58" );
	Show( "initialized" );
);
self:content = Function( {},
	{self, this},
	self = Namespace( "#58" );
	H List Box(
		meta:Button Box( "Hello World", "run" )
	,
		self:n days box = meta:Number Edit Box( 30, "run" )
	);
);
self:run = Function( {},
	{var1, var2, self, this},
	self = Namespace( "#58" );
	Show( "running" );
);
self:finalize = Function( {},
	{self, this},
	self = Namespace( "#58" );
	Show( "finalized" );
	Show( self:n days box << Get );
);
0;

To recap -- the meta programming takes any uscoped top-level variable and explicitly scopes it to `self`.  Then it takes any top-level function and adds a first line (which is `self = Namespace( <name of self> )`).  This is pretty simple conceptually and it turns out that with JSL this is pretty easy to implement.  There are unfortunately several branches depending on the form of the `Function()` function (whether it's 2 or 3 arguments, whether the 2nd argument is {Default Local} or simply a list of local variables)

 

 

So, what does all of this meta-programming get you?  Simply to avoid doing something equivalent, like below.  Note that the below script is mostly wrapped in the Eval( Eval Expr( ... ) ) statements.  this prevents you from being able to use such statements within any of the functions and so is not ideal for large projects.  Also, the display tree still has some ugly boiler-plate (the <<Set Function( Function( {this}, self = Namespace( <name> ) ... ) ) block is large and ugly)

self = New Namespace();

self:tool name = "Example";

Eval( Eval Expr(
self:initialize = Function( {},
	{Default Local},
	self = Namespace( Expr( self << Get Name ) );
	Show( "initialized" )
);

self:content = Function( {},
	self = Namespace( Expr( self << Get Name ) );
	H List Box(
		Button Box( "Hello World",
			<<Set Function(
				Function( {this},
					self = Namespace( Expr( self << Get Name ) );
					self:run
				)
			)
		)
	,
		self:n days box = Number Edit Box( 30, 4,
			<<Set Function(
				Function( {this},
					self = Namespace( Expr( self << Get Name ) );
					self:run
				)
			)
		)
	)
);

self:run = Function( {},
	{var1, var2, self},
	self = Namespace( Expr( self << Get Name ) );
	Show( "running" )
);

self:finalize = Function( {},
	self = Namespace( Expr( self << Get Name ) );
	Show( "finalized" );
	show( self:n days box << Get );
);
) );

If( self << Contains( "initialize" ), Try( self:initialize(), Print( "Error with self:initialize" ); Show( exception_msg ); ); );
Eval(
	Substitute(
		Expr(
			self:window = New Window( self:tool name,
				<<On Close(
					self = Namespace( _self_ );
					_window_return_value_ = 1;
					Try(
						_window_return_value_ = self:close
					);
					If( Not( Is Number( _window_return_value_ ) ) | Is Missing( _window_return_value_ )  | _window_return_value_ != 0,
						Try( self:destroy ); 
					);
					If( _window_return_value_ != 0,
						1
					,
						_window_return_value_
					)
				)
			,
				V List Box(
					Text Box( _self_, <<Visibility( "Collapse" ) )
				,
					self:main box = V List Box(
						self:container = self:content
					)
				)
			)
		)
	,
		Expr( _self_ ), self << Get Name
	)
);
Try(
	self:window << Inval << Update Window;
	Try( self:window << Bring Window to Front() );
);
Try( self:window << Set Window Icon( "<<put your icon here>>" ) );

Try( 
	self:finalize
,
	Print( "Error with self:finalize" );
	Show( exception_msg );
);

This script contains a lot of boiler-plate but is necessary when you want to have more explicit scope-control.  Notably, when you don't want to tell users "Only open one window at a time!".  Creating the meta-programming step is a bit tedious and definitely takes a lot of learning, but once you've figured it out it is very rewarding and also allows for much more succinct and maintainable code.

 

What I haven's showed here at all is the ability to create classes (completely separate from JMP14+ NewObject classes) that work very very well and allow interactive UI generation to be a breeze.

 

I know that this is a lot (and more to the point, far afield from any initial approach to building a UI script), but if you're creating more than one or two smallish UI scripts then the sunk time creating the meta-programming wrapper (which you only need to do once) will quickly be worth.

Jordan
SDF1
Super User

Re: JSL Help: Set Function() command syntax help

Hi @ErraticAttack ,

 

  Thanks for your reply and detailed post. It'll take me some time to work through it for sure, as I'm not a professional programmer, much of this is still new for me.

 

Thanks!,

DS

hogi
Level XII

Re: JSL Help: Set Function() command syntax help

Thank, @ErraticAttack  for the beautiful example how scoping works in Jmp.

 

It helped me a lot to get closer to understanding ...

Up to now, I could not understand what @drewfoglia meant with

hogi_1-1700916902331.png

in Essential Scripting for Efficiency and Reproducibility: Do Less to Do More (2019-US-TUT-290) 

You example nicely shows that this is not true.


Why does our experience here differ from Drew's statement? 

I think it's because the windows are created from the same JSL editor window.

After opening the first TEST window, it's very helpful that the script window still has access to the referenced objects teb and tb.

After opening the second TEST window, teb and tb reference now the objects in the second window - which apparently cripples the functionality of the first window (the entered text shows up in tb, i.e. in the second window).

 

So, linking both claims, what happens if both Windows don't share a common JSL Editor.
-> Both will have an independent NameSpace, like Drew anounced in his talk.

Options to do so:

a) close the Script and open it again, then create the second window

b) call the script from a menu:

hogi_0-1700916041518.png

hogi
Level XII

Re: JSL Help: Set Function() command syntax help

In my daily work, I face more the opposite issue:

Not: 2 windows which share the same Namespace

but: 2 scripts which only sometimes *) share a common Here Namespace.


When I include one JSL file in another one, at runtime, the included script shares the Here namespace of the main jsl file.
But when I want to debug some lines of code and run them from the second script - how can I give the included script_B access to the Here namespace of the main script? 2 seconds ago, when I executed the code of script_B it just HAD access to that Namespace.

 

https://community.jmp.com/t5/Discussions/Debugging-Jmp-applications/m-p/701121/highlight/true#M88566