All,
I have a following problem. I have a script that opens\closes multiple tables and windows, and I create several namespaces to handle this. I also delete those namespaces in <
I found that when I have two instances of the script running, closing main window in one of them deletes the namespaces and breaks the other script.
How do I make namespaces separate for each instance or, alternatively, how do I only allow one instance of the script\add-in running?
I understand that in
ns = New Namespace();
ns is a reference, and there is a unique name, like "#43" that is assigned every time a namespace is created. I can get that name by <ns in one running script instance is referring to "#43", and ns in another running instance is referring to, let's say, "#44"?
Depends on application. You could for example:
- Check if specific window name exists
- Use named namespaces and check if those already exist
and then perform do some action based on if they exist or don't.
Scripting Guide > Programming Methods > Advanced Scoping and Namespaces (help, jmp.com) is fairly good read
Names Default To Here(1);
// Delete Namespaces()
ns1 = New Namespace("AA");
show(ns1);
ns1 = New Namespace();
ns3 = New Namespace();
show(Get Namespace Names());
show(ns1);
If(Contains(Get Namespace Names(), "AA"),
ns1 = Namespace("AA");
);
show(ns1);
If(Contains(Get Window List() << Get Window Title, "window_title"),
Window("window_title") << Bring Window To Front;
Throw("Window already exists...");
);Doing proper scope control and resource management in JMP can be tricky -- the JMP objects DataTable(), and Namespace() are only ever explicitly released from memory and never by dereference.
For my purposes I have created a meta-programming library that can handle all the scope management and resource control necessary (as it is part of my work I cannot share the files), but something a little like it is here:
::modify display tree functions = Function( {self, display tree},
{Default Local},
box types = {"TextEditBox", "ButtonBox", "ListBoxBox"}; // Add others here
For( i = 1, i <= N Items( box types ), i++,
boxes = display tree << XPath( "//" || box types[i] );
For( j = 1, j <= N Items( boxes ), j++,
box = boxes[j];
function = Parse( Char( box << Get Function ) );
If( Try( Is Missing( function ), 0 ), Continue() ); // no function set
If( Head Name( function ) != "Function",
arg = Name Expr( function );
If( Head Name( arg ) != "Glue", // function is the right type -- a single call
new arg = Eval Expr( Glue( self = Namespace( Expr( self << Get Name ) ) ) );
Insert Into( new arg, Parse( "self:" || Char( Name Expr( arg ) ) ) );
Eval( Substitute( Expr(
box << Set Function( Local( {self}, __expr__ ) )
)
,
Expr( __expr__ ), Name Expr( new arg )
) )
)
,
arg = Arg( function, N Arg( function ) );
If( Head Name( arg ) != "Glue", // function is the right type -- a single call
new arg = Eval Expr( Glue( self = Namespace( Expr( self << Get Name ) ) ) );
Insert Into( new arg, Parse( "self:" || Char( Name Expr( arg ) ) ) );
Eval( Substitute( Expr(
box << Set Function(
Function( __args__,
{self},
__expr__
)
)
),
Expr( __args__ ), Arg( function, 1 )
,
Expr( __expr__ ), Name Expr( new arg )
) )
)
);
)
)
);
::inject self namespace = Function( {script},
{Default Local},
self = New Namespace();
script = Arg( script, 1 );
windows = {};
For( i = 1, i <= N Arg( script ), i++,
arg i = Arg( script, i );
If( Head Name( arg i ) == "Assign",
Remove From( script, i );
Insert Into( script,
Substitute(
Expr( Assign( _a_, _b_ ) )
,
Expr( _a_ ), Parse( "self:" || Char( Arg( arg i, 1 ) ) )
,
Expr( _b_ ), Arg( arg i, 2 )
)
,
i
);
arg i = Arg( script, i )
);
If( Head Name( arg i ) == "Assign" & As Name( Head Name( Arg( arg i, 2 ) ) ) == As Name( "New Window" ),
Insert Into( windows, Char( Arg( arg i, 1 ) ) )
);
If( Head Name( arg i ) == "Assign" & Head Name( Arg( arg i, 2 ) ) == "Function",
Remove From( script, i );
Insert Into( script,
Substitute( Expr(
Assign( _arg1_, Function( _args_,
{Default Local},
_script_
) )
),
Expr( _arg1_ ), Arg( arg i, 1 )
,
Expr( _args_ ), Arg( Arg( arg i, 2 ), 1 )
,
Expr( _script_ ), If( Head Name( Arg( Arg( arg i, 2 ), N Arg( Arg( arg i, 2 ) ) ) ) == "Glue",
Insert( Arg( Arg( arg i, 2 ), N Arg( Arg( arg i, 2 ) ) ),
Eval Expr( self = Namespace( Expr( self << Get Name ) ) ),
1
)
,
Eval Expr(
Glue(
self = Namespace( Expr( self << Get Name ) ),
Expr( Arg( Arg( arg i, 2 ), N Arg( Arg( arg i, 2 ) ) ) )
)
)
)
)
,
i
)
)
);
Insert Into( script, 0 );
Eval( script );
For( w = 1, w <= N Items( windows ), w++,
win = Eval( Parse( windows[w] ) );
::modify display tree functions( self, win );
Eval( Eval Expr(
win << On Close(
Local( {self = Namespace( Expr( self << Get Name ) )},
Try( self:on close );
Delete Namespaces( Force( 1 ), self )
)
)
) )
);
self
);
script = {
a = 1;
__tables = {};
add table = Function( {_table},
Insert Into( self:__tables, _table )
);
close tables = Function( {},
For( i = 1, i <= N Items( self:__tables ), i++,
Try( Close( self:__tables[i], No Save ) )
)
);
func = Function( {b},
Show( b + self:a )
);
on close = Function( {},
Print( "Closing!!" );
self:close tables;
);
run button = Function( {},
Print( "You pressed the button" );
table = Open( "$SAMPLE_DATA/Big Class.jmp" );
self:add table( table << Subset( All Rows, All Columns, Not Linked ) );
Close( table, No Save );
);
win = New Window( "teest",
List Box( {"A","B"}, <<SET function=""></SET>If you've already loaded the two global functions then you can keep them out of the script -- they serve to modify the functions within the namespace to be aware of which namespace they're in, as well as to modify the <<SET function=""> methods of the display tree to call the relevant functions within the scoping namespace. Also, this will automatically set the <CODE class=" language-jsl"><<ON close=""> method of the window to run the <CODE class=" language-jsl">self:on close() function (if it exists).
Note that this will, by default, delete the scoping self namespace when the window closes. This is not always the desired behavior, but for this simple demo it will suffice.
The last two lines of the script each open a window, but there is no cross-talk between them (you can load some tables by pressing the button, and when the window is closed the tables are automatically closed as well)
Hie @miguello,
As you pointed out you need the variable ns to be unique to each script, not just the namespace. @jthi solves this using Names Default to here(1). To define this explicitly, and perhaps better understand how it works, try running these two lines in a few different windows,
here:ns = New Namespace();
here:ns:a = random normal();
and then use
show namespaces()
to see see the value of a in each namespace. Calling
here:ns:a
from each individual window will return that window's value. In another window, run this:
Names default to here(1);
ns = New Namespace();
ns:a = random normal();
and note how it behaves the same way as the previous example. Try that without the here namespace and all reference to a will return the same value, indicating that they are from the same namespace.
So, just putting
Names default to here(1);
at the top of your script might fix your issue. You also might be able to avoid creating new named namespaces entirely by putting all of those variables you had there right in the here namespace instead.
When you say instances, do you mean that you're doing multiple includes? Or like a function?
Names default to here(1);
create_instance = function({ns},
Eval(EvalExpr(
nw = new window("Test",
window:ns = namespace(Expr(ns << Get name()));
<< On Close(
Delete Namespaces(force(1), window:ns);
),
Buttonbox("Press Me",
print(window:ns << Get name());
show(window:ns:x, window:ns:y);
)
)
));
);
create_instance(ns1 = new namespace({x=14;y=28}));
create_instance(ns2 = new namespace({x=140;y=280}));
This works for me.
*Edit* actually it's even easier. You don't even need the eval expr because it's not a runtime thing.
create_instance = function({ns},
nw = new window("Test",
window:ns = ns;
<< On Close(
Delete Namespaces(force(1), window:ns);
),
Buttonbox("Press Me",
print(window:ns << Get name());
show(window:ns:x, window:ns:y);
)
)
);
create_instance(ns1 = new namespace({x=14;y=28}));
create_instance(ns2 = new namespace({x=140;y=280}));