New Super Powers for Developers in 3.8
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
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:
// Specificy which query to use
// Set automatic query on start
// Make the search visible
// 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
// 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()
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
// Run the init function
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.
Wed, 12/21/2011 - 11:06#1
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 :)
Thu, 12/29/2011 - 09:59#2
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.