JSL Help: Set Function() command syntax helpHi 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(<BR />{this},<BR />this = cb << Get;<BR />If(<BR />this == 1,<BR />dt_results << Select Rows( neg_rows ) << Hide and Exclude;<BR />dt_results << Clear Select;<BR />dt_tb = dt_results << Get As Report();,<BR />this == 0,<BR />dt_results << Select Rows( neg_rows ) << Unhide;<BR />dt_results << Select Rows( neg_rows ) << Unexclude;<BR />dt_results << Clear Select;<BR />dt_tb = dt_results << Get As Report()<BR />);<BR />);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></SET>
Starting output, or whenever check box is state = 0.:

Desired output whenever check box is state = 1:

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
Data Blending and CleanupRe: JSL Help: Set Function() command syntax helpIn 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
Re: JSL Help: Set Function() command syntax helpThank, @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

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 boh 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:

Re: JSL Help: Set Function() command syntax helpHi @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
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: 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=""></SET>
@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=""></ON>
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=""></MORE></SOME>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=""></MORE></SOME>
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="">>" ) );
Try(
self:finalize
,
Print( "Error with self:finalize" );
Show( exception_msg );
);</ON>
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( )`). 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 < ) ... ) ) 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="">>" ) );
Try(
self:finalize
,
Print( "Error with self:finalize" );
Show( exception_msg );
);</SET>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.
Re: JSL Help: Set Function() command syntax helpHi @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> Training R².",
<<SET font="" size=""> 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></PARTITION></SET></MODAL>Re: JSL Help: Set Function() command syntax helpHi @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")))Re: JSL Help: Set Function() command syntax helpHi @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
Re: JSL Help: Set Function() command syntax helpNote that you cannot rely on JSL scope walking being consistent on <
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 <
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;
);
);
) );</RELEVANT>Re: JSL Help: Set Function() command syntax helpTry 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()
);
)
);