New Super Powers for Developers in 3.8

 

jrogelstad's picture

Right after every major release I like to draw attention to new features that may not be immediately evident or even included on the product roadmap. Now that 3.8 has been published as a release candidate I'd like to point out that the new Display class that was introduced in 3.7 has been exposed to our JavaScript interface in 3.8 so that creating new table-style reports is easier than ever.

The Display class was originally developed to make the table-style lists and reports found in our reports menus easier to build and more consistent. Most of the reports and lists in the C++ code were converted to use this class in 3.7 and we're hoping to take care of a few remaining outliers in 3.9. In 3.8 we converted all the display reports in the Manufacturing Edition, which is an extension package, to use the same class which means now you can build your own displays with JavaScript.

The Display class includes a pre-defined toolbar on the top of the window with standard button options such as "New", "Close" and "Query" and a built in XTreeWidget which is the table-style list that you can populate with data defined in a MetaSQL query. Many of the topics I just mentioned are expansive; here I just want to give a basic example of how the Display class can be leveraged with very little code.

What we're going to do here is build something that doesn't currently exist in the application which is a searchable list of all the privileges in the system including descriptions. There will be three files we need to build:

  • The MetaSQL that queries the list from the database
  • A JavaScript file that sets up the display
  • A JavaScript file that adds the menu option to the application

First we're going to build the query. Again, describing all the details of how MetaSQL works is an expansive topic you can read about here and here. This query is simply going to pull everything we need right off the privileges table which is called "priv." To make the query useful, we're going to add the logic so the query can filter on search criteria and a specific module selection.  Here's what the query looks like:

SELECT priv_id, priv_module, priv_name, priv_descrip
FROM priv
-- If we're searching for a specific module, this does a join on a "virtual table"
-- created using windowing functions available in PostgreSQL 8.4.
<? if exists("module_id") ?>
JOIN
(SELECT row_number AS module_id, priv_module AS module_name
FROM (
  SELECT priv_module, row_number() OVER ()
  FROM (
    SELECT DISTINCT priv_module
    FROM priv
    ORDER BY priv_module ) data1
  ) data2
  ) module ON (module_name=priv_module)
<? endif ?>
WHERE true
-- This clause handles filtering if the user wants to search the list
-- The ~* uses PostgreSQL built in support for Regular Expressions
<? if exists("search_pattern") ?>
  AND priv_module || ' ' || priv_name || priv_descrip ~* <? value("search_pattern") ?>
<? endif ?>
-- This clause filters down to a specific module based on the join included above.
<? if exists("module_id") ?>
  AND module_id = <? value("module_id") ?>
<? endif ?>
ORDER by priv_module, priv_name

This isn't the simplest example I could have come up with because of the complicated query required to perform filtering on a specific module, however it does showcase the windowing function features available in PostgeSQL 8.4 that we can use now that 8.4 is the minimum database required for xTuple. Basically the situation is that we want to be able to filter on a specifc module, but for that to work we need a list of modules that has an ID like a table, but there is no such table in xTuple. The windowing function in the query above builds a query with a result that looks just like a table.

So in xTuple 3.8.0 you can go to System > Design > Metasql and click "New" and paste the query above right into the window. File > Save As (Database) and type in the data as pictured:

 

 

If you are not logged in as the database admin the application might complain that you can't set the query to grade 0. In that case just set it to 1.

Your MetaSQL should now look like this:

 

 

 

Next we're going to add in some JavaScript to handle configuration of our display:

 


 

// Specificy which query to use

mywindow.setMetaSQLOptions('privileges','detail');
 
// Set automatic query on start
mywindow.setQueryOnStartEnabled(true);
 
// Make the search visible
mywindow.setSearchVisible(true);
 
// Add in the columns
var _list = mywindow.list();
 
_list.addColumn(qsTr("Module"), 100, Qt.AlignLeft, true, "priv_module");
_list.addColumn(qsTr("Name"), -1, Qt.AlignLeft, true, "priv_name");
_list.addColumn(qsTr("Description"), -1, Qt.AlignLeft, true, "priv_descrip");
 
// Add filter criteria
 
// This says we want to use the parameter widget to filter results
mywindow.setParameterWidgetVisible(true);
 
// Create a fancy query using a PostgreSQL windowing function to treat
// module as a virtual table
var qryModule = "SELECT row_number AS module_id, priv_module AS module_name "
+ "FROM ( "
+ " SELECT priv_module, row_number() OVER () "
+ " FROM ("
+ " SELECT distinct priv_module "
+ " FROM priv "
+ " ORDER BY priv_module ) AS data "
+ ") AS module ";
 
// Add the combo box
mywindow.parameterWidget().appendComboBox(qsTr("Module"), "module_id", qryModule);
 

Go to System > Design > Scripts and click "New." Copy and paste in the text above, give the file the name "dspPrivileges" and make sure it is enabled as pictured:

 

 

Finally, we're going to add a menu action to the system to launch this display. Note this part is critical because we actually have to launch the script as a Display so the script code above knows "mywindow" is a Display and should have access to all the neccessary support structures of one:

 


// Set up a namespace object to avoid conflicts with other scripts and extensions

var example = new Object;
 
// Create a function to launch our display window
example.privilegesDisplay = function()
{
toolbox.newDisplay("dspPrivileges");
}
 
example.init = function()
{
// Get system menu
var systemMenu = mainwindow.findChild("menu.sys");
 
// Create a new action for the system menu
tmpaction = systemMenu.addAction(qsTr("Privileges"), mainwindow);
 
// Give it a name. This can be accessed by hotkeys!
tmpaction.objectName = "sys.privileges";
 
// Connect the menu action to the privileges display function
tmpaction.triggered.connect(example.privilegesDisplay);
}
 
// Run the init function
example.init();

Create a new script for the code above using the name initMenu. xTuple looks for and runs all scripts with that name when the application starts. Your screen should look like this:

 

 

Now close and restart xTuple. You should see a new menu item called "Privileges." Click that menu item and you should see a display that looks like this:

 

 

 

Try narrowing the list by entering search criteria. Then click "More" and search on a specific module. Note you can right click on the results and copy or export the data just like any other xTuple window. This is super nice because most all of the complicated stuff such as the screen layout and button handling is taken care of by the Display class.

 

Of course there is much, much more you can do with it and if you have the manufacturing edition you can see many JavaScript examples of how it can be used in 3.8. Even if you don't have access to that, there are literally hundreds of examples of the display class being used in the C++ PostBooks core Hopefully I've been able to convey the idea that once you learn your way around the Display class and associated tools you can create a very nice reporting interface without a lot of coding.

 
gpitel's picture
Offline
Joined: 04/13/2010
Thanks

Thanks for highlighting this new feature. As you may have guessed the average user does not get much opportunity to trudge through API changes . When a developer points something out on the main company page... I usually pay attention :)

 
davidbeauchamp's picture
Offline
Joined: 07/20/2008
Awesome

Just wanted to say it is great, really great. I was able to take 4 legacy style screens I had made and converted them all over in about 20 minutes.

Cheers!