While updating the Semiconductor Toolkit, I looked for a better way to rebuild the Recall function.
Open the Distribution platform in JMP. Look at that little group of buttons on the right – specifically, the “Recall” button. It’s everywhere. Almost every platform in JMP has it. It’s baked into the DNA of the JMP design language. Now think for a second about how often that little button has saved your fanny in the heat of your daily battle with data. A lot, right? It’s probably one of the capabilities in JMP that is most taken for granted. But I would submit that it’s one of the myriad little touches that make JMP as useful as it is.
Now, JSL scripters, think for a second about replicating that Recall function in a custom add-in. It’s a pretty advanced task – the way I’ve seen most people do it (myself included) would involve creating a variable in a custom or global namespace for each GUI element you want to recall. Then there are the support functions to gather the information from each GUI element at runtime and the ones to write the information back when the Recall button is pressed. It. Is. A. Royal. Pain. Setting up a recall capability in JMP scripts and custom applications is probably one of the most laborious tasks out there. For one or two components in a GUI, it’s not a big deal, but the standard process doesn’t scale well. And yet, for that effort, a recall capability imparts a level polish and ease of use to an add-in that elevates it to a new level of professionalism. Users love it when there is a recall capability in an add-in. And, to be blunt, I hate coding it.
We can rebuild it
Let me back up a little. The idea this post is discussing started in early 2016. I had just started updating the Semiconductor Toolkit (STK) to version 2. For various reasons that I’ll cover in another blog post, I decided to migrate the whole project to Application Builder in JMP. This meant a lot of tinkering with the code base and GUIs, tidying things up, standardizing code wherever possible and adding some new capabilities contributed by colleagues (thanks again, guys!). Unfortunately, this work broke all the existing code for Recall from version 1. Faced with the task of rebuilding the Recall code for all the platforms in the add-in, I thought, “There’s got to be another way to approach this problem. There has got to be a way to reduce all that repetitive coding.”
Around the same time, I was studying how to use Associative Arrays (AAs) in JSL for some other capabilities in the STK. I don’t see people using them much, but AAs can be an incredibly powerful tool in your scripting arsenal. For the sake of this discussion, an AA is a special kind of list. You can search it, put stuff in, take stuff out, etc. What makes them special is that each entry in the list has two parts, a Key and a Value. There are some great blog posts and resources out there on AAs (JMP Documentation On Converting a List of Associative Arrays to a Data Table JSL Tip: Finding Unique Values In a List ) that I reference when I’m working with AAs. They cover a lot of the material on AAs and have some great examples. Going back to my original pain point, it occurred to me that the global variable name and its contents could probably be translated into a Key-Value pair in an AA. So, being my muse of the moment, I wondered if I could use an Associative Array to build a better Recall.
OK, to help make some sense about what I was thinking, let’s look at how this is normally done. There are two basic operations required to implement Recall. When the user performs some triggering action, i.e., clicks “OK”, all the GUI elements, i.e., list boxes, radio boxes, text boxes, etc., to be recalled need to have their contents pulled and stored in some way. Then, when the user clicks the “Recall” button, the stored contents need to be pushed back to the appropriate places in the GUI. At the heart of these two operations are a pair of JSL messages: << get() and << set(). There are different flavors of these depending on the GUI element (don’t ask me why – I didn’t write JSL – I just use the stuff) but they are all basically just variants on these two and do the same things. The scripting index in JMP (Help > Scripting Index) is a great way to keep straight which one goes with which GUI element.
So, my AA-based solution has to do a number of things. First, I have to be able to use << get() and << set() in all their permutations. I have to be able to tell it what GUI elements I want to save. And, I have to have somewhere to save this information that doesn’t disappear when the add-in is done running. This means the revised workflow would involve building an AA using a list of the GUI elements I want to restore. I would need to put this AA into either the Global or some custom namespace (more on that in a second). As I thought about constructing the AA I came to a question, “If I use the GUI elements as Keys in my AA, what would I use for the Values in my Key-Value pairs?” I realized that, because of the way AAs work, I could just leave them empty, i.e. use "Empty()" as my value, but that felt like such a waste of an opportunity for some reason. As I thought about this more, I realized that, if I were to put the default values for the GUI elements into the AA Key-Value pairs, I could actually set up a “Reset” function (that would restore the GUI to its default state) using the same code! I could support two features for the price of one! And, who doesn’t love a good two-fer?
Yes, namespaces are useful
Back to the issue of namespaces for a second. Namespaces, for the uninitiated, may seem like this mystic, mysterious, thing that they hear about at JMP Discovery Summit or occasionally on a blog post. Let me pop that bubble right now – they ain’t that special. They are, however, incredibly useful, and the best JSL scripters have learned to use them effectively. (To be clear, I don’t count myself in that group, but I do eat lunch with them occasionally at SAS HQ or Discovery Summit … so maybe that counts for something.)
To give you a picture of what a namespace is and does, let’s look at dictionaries. If you are writing something in English and needed to look up a word, you would use an English Dictionary. If you were to start working in French and needed to look up a word, you would use a French dictionary. Sometimes, thanks to the Norman Conquest of 1066, the same word could appear in both dictionaries but might have slightly different meanings. This wouldn’t generally be a problem because you have the document context to help you out. If you’re working on a French document, you reach for the French dictionary – no problem. It’s not even a problem if you have a mix of French and English in the same document as long as you have some context clues to help you figure out what dictionary to reach for. What does this have to do with JSL? Namespaces are like dictionaries. They are places that you can put custom variables and functions that give JMP extra context to understand what you want it to do. For example, in the STK, I use a function name annotateWindow() in several places. In fact, I use a function with this name in every part of the software where I draw something on an existing window. In total, I probably have half a dozen functions with the name annotateWindow(). The wrinkle is that each version does something slightly different. But, because each one exists in its own namespace, this is never a problem. Even if I have two different GUIs open at the same time. JMP knows that when I’m working in one namespace I expect annotateWindow() to do one thing and when I’m in another I expect it to do something else. This strategy significantly simplified the GUI design process in the STK. So, namespaces let you provide context for JMP to know which version of a function you want it to use in a particular script. They can also exist in memory after the add-in that created them has closed. And it’s these two points that make them particularly useful to our Recall solution.
It's time to code
OK, enough theory – let’s hack some code. Besides, the rest of this will make more sense if I explain it as I go. First, I’m an advocate for using libraries in my scripts, it’s best practice in most development situations. Most programming languages can do it – JSL can, too. So, my Recall solution will be built in the form of a library. To use the library in an application or script the developer just needs to use an include() argument at the start of the script with a file path to the library.
Second, I’m a fan of chatty functions. It’s really handy to have the script telling you what it’s doing every step of the way. But, I also want to be able to tell my chatty functions to shut-it when I’m done testing and coding. (Chatty functions can slow down your code execution if you’re not careful). If you look at the actual library in the File Exchange, you’ll see a lot of write() and show() arguments. These are contained in an If(verbose == 1, <…>) statement that lets me turn them off by passing a verbose = 0 command to the function. I use this construct a lot in my scripts, and I’ll usually just put the verbose variable at the top of the script so that I can turn the verbose logging on and off for the whole script by adjusting one parameter. To keep the code short here, I’ve removed these from the snippets below. The completed library can be found in the Recall Function Library.
The first function in the library is called genArrays(). This function makes an AA from a list of elements in the GUI and a list of their default values. The function actually makes two copies of the AA – one to support the Recall functions, called ::<platform name>UserSettings, and one to support the Reset functions, called ::<platform name>Defaults. These two Arrays are saved into the Global namespace. The “::” operator at the front of a variable name does this. The other thing to notice is that I’m using an Eval(Parse()) structure. This is a common strategy for building dynamic JSL that might be cumbersome using substitution schemes. The last thing to notice is that I lock the user settings variable. This is to prevent the AA or any of the values from being accidentally overwritten.
genArrays = Function( {objects, values, platformName, verbose = 1},
{Default Local},
// Create the defaults Array
defaultString = "::" || platformName || "Defaults = Associative Array( objects, values, Empty() )";
defaultStringName = "::" || platformName || "Defaults";
// Run the default command
Eval( Parse( defaultString ) );
// Create the defaults Array
userString = "::" || platformName || "UserSettings = ::" || platformName || "Defaults";
userStringName = "::" || platformName || "UserSettings";
// Try to create the user settings Array and lock it so it's not overwritten.
Try(
Eval( Parse( userString ) );
Eval( Parse( "Lock Globals( " || platformName || "UserSettings )" ) );
,
Write(
"\!N\!NThe Associative Array \!"" || platformName || "UserSettings\!" is already present in memory and ready for use.\!N\!N"
)
);
);
Let’s take a second and look at what goes in the lists that are used to construct the AA. When I was figuring out how to approach this problem, I had to deal with the issue that there are several flavors of the << get() and << set() arguments that I needed to handle. What I came up with was to add a header to the Key part of the Key-Value pair in the AA. These are one or two letters that are put in front of the key (the GUI element). For GUI elements that use << set() the header is s^. The << Set Items() and << Set Text() use si^ and st^ respectively. If I had a GUI (we’ll call it “neatGUI”) with a text box (tb1), a number box (nb1), and a list box (lb1) the objects list would look like this:
objects = {st^tb1, s^nb1, si^lb1};
If we said that we wanted these to have specific values when the script ran or when the user pushes a Reset button, we could set up the values list with what we wanted. I’m going to use an Empty() command for the list box to clear the contents.
values = {“hello world”, 42, Empty()};
Now that we’ve got our two lists, we can use the GenArrays() function to generate the arrays like this:
GenArrays(objects, values, “neatGUI”);
This will create the two AAs in the Global namespace. At creation, they would both look like this:
[s^nb1=>42, si^lb1=>Empty(), st^tb1=>”hello world”]
Now, all we have to do is provide the next three functions with the same GUI name (neatGUI) and it will take care of the rest! Let’s look at what they do.
The first of the functions that actually interacts with the GUI is resetRoles(). This function is basically a loop that pulls each key and value in the default AA and uses the information to reset the GUI elements to the default values. Since AA’s are “special” lists we need to use different instructions to work with them. So, instead of just getting the first value in the AA using a subscript, I use the << First message. From here, it’s just separating the header part from the GUI element in the key. These pieces are passed to some logic, so I use the right << set() message with the value from the AA for the current key. Finally, this is all executed using another Eval(Parse()) argument. The whole process then repeats for each Key-Value pair in the AA.
resetRoles = Function( {platformName, verbose = 1},
{Default Local},
// Get the AA for the provided name
arrayName = Eval( Parse( "::" || platformName || "Defaults" ) );
arrayNameString = "::" || platformName || "UserSettings";
// Setup a logical break if an AA isn't provided
Try(
// Start the function
// Check if arrayName is an AA
If( Is Associative Array( arrayName ),
//Initialize the counter
currentKey = arrayName << First;
// Loop through the keys
For( i = 1, i <= N Items( arrayName ), i++,
// Extract the header and the key
stAA = currentkey; // The header-key pair
stType = Word( 1, stAA, "^" ); // The header part
stArg = Word( 2, stAA, "^" ); // The key part
// Match the operation header to the correct operation instruction and build the string
stArgString = Match( stType,
"si", stArg || " << Set Items( " || Char( arrayName[currentkey] ) || " )",
"s", stArg || " << Set( " || Char( arrayName[currentkey] ) || " )",
"st", stArg || " << Set Text( \!"" || Char( arrayName[currentkey] ) || "\!" )"
);
// Eval-Parse the string
Try( Eval( Parse( stArgString ) ) );
// Get the next key in the AA
nextkey = arrayName << Next( currentKey );
// set it as the target key for the next loop
currentkey = nextkey;
);
,
// Stop execution if not an AA.
Throw()
);
,
);
);
The other two functions, storeRoles() and recallRoles(), work in a similar way with a few small differences. First, they both operate on the user AA instead of the default AA created by GenArrays(). Second, storeRoles() uses << get() messages and overwrites the value part of the Key-Value pair for a GUI element so it’s stored for later recall. There is also some code included to unlock and relock the user AA during editing. Last, the recallRoles() function uses the << Append() message with the si^ header instead of the << set() message. Rather than put the code here, I'll refer you to the library in the File Exchange.
So, after some thinking, coding, liberal application of the delete key (backspace for those stuck in the Windows world … my condolences), and a little more coding, I learned it’s entirely possible to build a set of functions to support Recall/Reset using an Associative Array. In hindsight, for a simple two- or three-element GUI, this whole process is, admittedly, a bit Rube-Goldberg-esque. But, imagine a GUI with 10 or 20 elements. This approach scales so much better than the traditional approach. You only have to keep track of a list of what you want to recall/reset and their initial values. And, you don’t even need to sport a heavy Austrian accent or fight a power-hungry governor for control of Mars to do it.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.