{{Format}}
== Overview ==
This lesson teaches you how to add simple calculator tools for your users by combining a complex, data-aware builder script with much simpler data schema files and calculation scripts. It steps away from map-based functionality to show what can be done using basic Platform tools, and also demonstrates the separation of standard code, module configuration, and mapset configuration.
This tutorial is part of the [[Course: Partner Workbench]].
=== audience === * system administrators and IT staff * power users * developers
=== objectives === * create a module with a script library * create configuration files for the module * create a modular mapset dependent on the module
=== prerequisites ===
You need a working Partner installation and basic familiarity with the Workbench. You should at least have finished the [[Tutorial: Making a Mapset]] and its prerequisites.
== Enough With The Spreadsheets ==
A very common pattern amongst Partner customers is the rule-of-thumb calculation tool. This may be as simple as a laminated card or standard employee book, or as complex as a spreadsheet with custom calculations or even a custom-written application.
With a little thought, almost any such thing could be converted into an automated, form-based tool. It’s like any other math problem - define some input variables, some output variables, and the formula to determine one from the other and there you go.
In this case the GUI form coding and data management is really the hardest part of the problem. Therefore, we’ll split the problem up into two major parts. One is that hard part - generating a GUI from a data schema, and handling inputs and outputs and other behavior. The other is the actual calculations we want to run, and we’ll make those modular so that we can have as many as we like and keep them nice and simple. Ideally the calculation scripts should read just like math problems so you can focus on the algorithm and not the syntax.
For additional realism, we’ll put the individual calculation files in the config/ hierarchy, and the standard management code in a module.
Let’s get started.
== Make the Module ==
In the Workbench, go to the modules/seat directory, select the Modules tab, and click the New Module link. Call it MyCalculators (don’t call it just Calculators; Partner might make one named that if this is a popular tutorial hehe).
Now, create the standard directory scriptlib/ using the link on the Module tab for your new module.
In that directory, add the following file:
==== modules/seat/MyCalculators/scriptlib/CalculatorLib.bsh ==== <pre> import com.partnersoft.cog.*; import com.partnersoft.system.*; import com.partnersoft.gui.forms.*; import com.partnersoft.gui.*; import javax.swing.*; import com.partnersoft.formats.csv.*; import com.partnersoft.scripting.*; import com.partnersoft.data.*; import java.lang.reflect.*;
module = SystemServices.moduleManager.moduleNamed(“MyCalculators”); calculatorsDir = module.getConfigDirectory().directoryNamed(“calculators”);
}
schema = new CogSchema(CogBootstrapSchema.singleton());
csvFiles = calculatorsDir.listFilesWithExtension(”.csv”); for (csvFile : csvFiles) {
calculatorName = csvFile.getBaseName(); inputFields = new ArrayList(); outputFields = new ArrayList(); source = new CsvDataRecordSource(csvFile); for (record : source) {
- field = new CogStructureField(
- (String)record.get(“name”), (String)record.get(“group”), (String)record.get(“label”), (String)record.get(“description”), (String)record.get(“type”) );
- if (field.getGroup().equalsIgnoreCase(“input”))
- inputFields.add(field);
- else
- outputFields.add(field);
} inputStruct = new CogStructureType(calculatorName + “-input”, “com.partnersoft.data.Naming”, “Form”, null, inputFields); schema.addType(inputStruct);
outputStruct = new CogStructureType(calculatorName + “-output”, “com.partnersoft.data.Naming”, “Form”, null, outputFields); schema.addType(outputStruct);
}
return schema;
}
}
private Form inputForm; private Form outputForm; private Script script;
this.inputForm = inputForm; this.outputForm = outputForm; this.script = script;
inputForm.addFieldListener(this);
}
log.info(“urpdate”); try {
inputForm.apply(); input = inputForm.getEditedCog(); output = outputForm.getEditedCog(); script.setVariable(“input”, input); script.setVariable(“output”, output); for (node : input.getChildren()) {
script.setVariable(node.getName(), node.getData());} script.run(); log.info(“Edited type = ” + outputForm.getEditedType()); for (field : outputForm.getEditedType().getFields()) {
Object value = script.getVariable(field.getName()); output.putObject(field.getName(), value);} outputForm.reset();
} catch (Throwable oopsie) {
log.error(“Error during update”, oopsie);
}
}
} catch (Throwable oopsie) {
- while (oopsie != null) {
- log.error(“blah”, oopsie); oopsie = oopsie.getCause();
}
}
} public void actionRequested(FormFieldEvent event) {
urpdate();
}
}
schema = loadSchema(); scriptKeeper = loadScripts(); Script script = scriptKeeper.scriptFor(“calculators/” + calculatorName); if (script == null) {
throw new IllegalArgumentException(“Unknown calculator: ” + calculatorName + ”; available = ” + scriptKeeper.listScriptsIn(new Path(“calculators”)));
}
Form inputForm = buildFormFor(schema, calculatorName + “-input”); Cog inputCog = new Cog(schema, calculatorName + “-input”); inputForm.setEditedCog(inputCog);
Form outputForm = buildFormFor(schema, calculatorName + “-output”); Cog outputCog = new Cog(schema, calculatorName + “-output”); outputForm.setEditedCog(outputCog.getRoot());
innerEar = new UpdateEar(inputForm, outputForm, script);
window = new MainWindow(calculatorName + ” Calculator”); panel = new JPanel(); panel.setLayout(new FlexGridLayout(1, 2)); panel.add(inputForm.getGui(), “West”); panel.add(outputForm.getGui(), “East”); window.add(panel, “Center”); window.pack(); PreferenceManager.singleton().manageWindowPreferences(window.getTitle(), window); window.show();
} </pre>
Yes, it’s a very complicated library script. Luckily, understanding it is extra credit - we don’t need to know nearly so much to get it to work.
== Restart ==
Restart the Workbench so that your module gets loaded.
== Configure the Module ==
OK, now configure the module. Create the directory config/seat/modules/MyCalculators/ if it doesn’t exist, and then add a subdirectory named calculators/.
This directory will contain the calculator configuration - a CSV describing the input and output fields, and a .bsh script with the actual math in it.
Here are two examples.
==== config/seat/modules/MyCalculators/calculators/Pythagorean.csv ====
<pre> name,group,label,description,type a, Input, Side A, Length of side A of right triangle., Number b, Input, Side B, Length of side B of right triangle., Number h, Output, Hypotenuse, Length of hypotenuse of right triangle., Number </pre>
==== config/seat/modules/MyCalculators/calculators/Pythagorean.bsh ====
<pre> h = Math.sqrt(a * a + b * b); </pre>
==== config/seat/modules/MyCalculators/calculators/Electrical Current.csv ====
<pre> name,group,label,description,type v, Input, Voltage, Voltage in volts,Number i, Input, Current, Current in amps,Number r, Input, Resistance, Resistance in ohms,Number v, Output, Voltage, Voltage in volts,Number i, Output, Current, Current in amps,Number r, Output, Resistance, Resistance in ohms,Number </pre>
==== config/seat/modules/MyCalculators/calculators/Electrical Current.bsh ====
<pre> if (v == 0) {
- if (i != 0 && r != 0)
- v = i * r;
} else if (i == 0) {
- if (v != 0 && r != 0)
- i = v / r;
} else if (r == 0) {
- if (v != 0 && i != 0)
- r = v / i;
} </pre>
== Create a Mapset ==
OK, we could use an app to launch these, but most likely our end-users will want these available from menus in the Map Viewer.
Create a mapset in seat named “Calculators”. In it, create a file info/module.txt with the name of our module.
==== config/seat/mapsets/Calculators/info/module.txt ====
<pre> MyCalculators </pre>
This is now a “modular” mapset - it derives from a module and can use code from that module.
Create the actions/ directory and action scripts for each of our calculators:
==== config/seat/mapsets/Calculators/actions/Electrical Current.bsh ====
<pre> script.include(“CalculatorLib.bsh”); launchCalculator(script.getName()); </pre>
==== config/seat/mapsets/Calculators/actions/Pythagorean.bsh ====
<pre> script.include(“CalculatorLib.bsh”); launchCalculator(script.getName()); </pre>
Notice that the files have the same contents - they actually use the name (script.getName()) of the file to select the calculator to use.
== Test ==
Start the Map Viewer from the Workbench and your (hopefully working) calculators should be the menu under Actions/Calculators/.
[[Category: Tutorials]]