Overview

With version 3.0 of the xTuple Application we have added support for QtScript. QtScript provides us with a way to load UI forms using ECMAScript based language files for creating and modifying application functionality without the need to recompile. To take advantage of this functionality, one should have a basic understanding of how to program using ECMAScript and create UI screens using QtDesigner.

There are a couple of resources you will need to take full advantage of the scripting functionality. First, you should read the Qt Script Documentation to get an idea of what the scripting is and how it works within Qt. If you are creating UI forms, you should also be familiar with how to create screens using QtDesigner, reading the Qt Designer Documentation if necessary. Finally, you should be familiar Qt's general documentation.

All of the Qt classes and widgets are documented and provide various functionality. As part of the scripting environment, those widgets may provide some functionality to the scripting environment. As discussed in the Qt Script Documentation, any properties, public slots, signals, and sometimes additional scriptable functions are available when accessing an object of that type. This is the base functionality that can be taken advantage of by default. Where this functionality is desired but not available, we have added helper functions or provided wrapper classes to augment the available functionality.

Resources:

Basics

Creating Scripts

Basically a script is just some code. The key important element is that it is interpreted rather than compiled, so scripts can be added to the xTuple ERP application or changed at any time; you don't need to wait for a new software release to modify the application's behavior. Each script is nothing more than a text file with commands that are processed by the application. In order for the application to use the script, though, you will need to load the script into the database and give it a name. The name is significant! When you open a screen, as we will call them, the screen has a system name. As part of the process for showing that screen, the application looks for any scripts that match the screen's system name. For the time being (xTuple ERP versions 3.0 and 3.1) the only way to find a screen's system name is to look at the C++ application source code.

To actually load the script into the database, you can use the menu "System | Design | Scripts..." to edit or create a new script. Scripts can be enabled and disabled. You may also have several scripts with the same name and different rank (i.e., order), so you know which one will run first. You can either type in the script code, paste the script code, or use the convenient IMPORT button.

Creating Custom Screens

Custom Screens are screens that you can design, using Qt Designer, and load into a database. A screen is a .ui form which specifies widgets and layout information. Just like scripts, you can update and create screens in the database using the the menu "System | Design | Screens...". Screens have names, which are used to find matching scripts. Like scripts, screens can be enabled or disabled as well as have an order. There are two differences with the way screens are setup and work. First, while you may have several screens with the same name, only the lowest-numbered enabled screen is used. Second, you must export or import the ui file (i.e., you cannot edit the ui file directly on the database).

Because forms are very limited in what they can be made to do without code or scripting, forms will almost always require one or more scripts.

Making Custom Screens accessible to users

Creating a form does not make it available to users. In order to have a form accessible to users on the menu system, you will need to create a custom command. Custom Commands can be used to add menu items on the various Module Menus to execute external applications or load Custom Screens. In addition, Custom Commands can be assigned Privileges; a user must be granted those privileges in order to use those commands. By default, Custom Command menu items show up in a sub-menu called "Custom" but this can be changed using a special script that will be discussed later.

Scripts

When scripts are processed they automatically have three objects made available globally to them. These objects are:

In addition to these objects there is also a helper class called ScriptQuery that can be used to access query result information. The ScriptQuery object is returned by the executeQuery() method of ScriptToolbox.

Object mainwindow

The object mainwindow provided to all scripts is the GUIClient class of the application and contains the MDI interface as well as the full menu structure. Any child of this object is accessible by using the findChild() method, as described in the QtScript documentation. Menu items are the most useful object you can gain access to through this object, but there are several other properties and methods that can be accessed, as well.

First, any properties, signals, and public slots of the QMainWindow class and its parent classes are available. In addition, there are several other slots and signals that can accessed from this object specifically provided for the xTuple application.

Public Slots

sReportError(const QString &);

This slot writes a debugging message. See Script Debugging below.

sTick();

This slot is typically used internally by the application and should rarely be used in scripts. It checks the connection to the database server and updates the user's event notification icon.

sChecksUpdated(int, int, bool);

This slot tells other open windows that this script has updated the definition or status of one or more bank Checks.

sAssortmentsUpdated(int, bool);

This slot tells other open windows that this script has updated the definition or status of one or more Assortments.

sBBOMsUpdated(int, bool);

This slot tells other open windows that this script has updated the definition or status of one or more Breeder Bills of Materials.

sBOMsUpdated(int, bool);

This slot tells other open windows that this script has updated the definition or status of one or more Bills of Materials.

sBOOsUpdated(int, bool);

This slot tells other open windows that this script has updated the definition or status of one or more Bills of Operations.

sBudgetsUpdated(int, bool);

This slot tells other open windows that this script has updated the definition or status of one or more Budgets.

sBankAdjustmentsUpdated(int, bool);

This slot tells other open windows that this script has updated the definition or status of one or more Bank Adjustments.

sBillingSelectionUpdated(int, int);

This slot tells other open windows that this script has updated the definition or status of one or more Billing Selections.

sCashReceiptsUpdated(int, bool);

This slot tells other open windows that this script has updated the definition or status of one or more Cash Receipts.

sConfigureGLUpdated();

This slot tells other open windows that this script has updated the General Ledger configuration.

sCreditMemosUpdated();

This slot tells other open windows that this script has updated the definition or status of one or more Credit Memos.

sCrmAccountsUpdated(int);

This slot tells other open windows that this script has updated the definition or status of one or more CRM Accounts.

sCustomCommand();

This slot tells other open windows that this script has updated the definition or status of one or more Custom Commands.

sCustomersUpdated(int, bool);

This slot tells other open windows that this script has updated the definition or status of one or more Customers.

sGlSeriesUpdated();

Use this slot to tell other windows in the application that this script has updated the definition or status of one or more General Ledger Series.

sInvoicesUpdated(int, bool);

This slot tells other open windows that this script has updated the definition or status of one or more Invoices.

sItemGroupsUpdated(int, bool);

This slot tells other open windows that this script has updated the definition or status of one or more Item Groups.

sItemsUpdated(int, bool);

This slot tells other open windows that this script has updated the definition or status of one or more Items.

sItemsitesUpdated();

This slot tells other open windows that this script has updated the definition or status of one or more Item Sites.

sPaymentsUpdated(int, int, bool);

This slot tells other open windows that this script has updated the definition or status of one or more Payments.

sProjectsUpdated(int);

This slot tells other open windows that this script has updated the definition or status of one or more Projects.

sProspectsUpdated();

This slot tells other open windows that this script has updated the definition or status of one or more Prospects.

sPurchaseOrderReceiptsUpdated();

This slot tells other open windows that this script has updated the list of Receipts.

sPurchaseOrdersUpdated(int, bool);

This slot tells other open windows that this script has updated the definition or status of one or more Purchase Orders.

sPurchaseRequestsUpdated();

This slot tells other open windows that this script has updated the definition or status of one or more Purchase Requests.

sQOHChanged(int, bool);

This slot tells other open windows that this script has updated the Quantity on Hand of some Item Site.

sQuotesUpdated(int);

This slot tells other open windows that this script has updated the definition or status of one or more Quotes.

sReportsChanged(int, bool);

This slot tells other open windows that this script has updated the definition or status of one or more Reports.

sReturnAuthorizationsUpdated();

This slot tells other open windows that this script has updated the definition or status of one or more Return Authorizations.

sSalesOrdersUpdated(int);

This slot tells other open windows that this script has updated the definition or status of one or more Sales Orders.

sStandardPeriodsUpdated();

This slot tells other open windows that this script has updated the definition or status of one or more Standard Periods.

sTaxAuthsUpdated(int);

This slot tells other open windows that this script has updated the definition or status of one or more Tax Authorities.

sTransferOrdersUpdated(int);

This slot tells other open windows that this script has updated the definition or status of one or more Transfer Orders.

sVendorsUpdated();

This slot tells other open windows that this script has updated the definition or status of one or more Vendors.

sVouchersUpdated();

This slot tells other open windows that this script has updated the definition or status of one or more Vouchers.

sWarehousesUpdated();

This slot tells other open windows that this script has updated the definition or status of one or more Sites or Warehouses.

sWorkCentersUpdated();

This slot tells other open windows that this script has updated the definition or status of one or more work centers.

sWorkOrderMaterialsUpdated(int, int, bool);

This slot tells other open windows that this script has updated the definition or status of work order materials.

sWorkOrderOperationsUpdated(int, int, bool);

This slot tells other open windows that this script has updated the definition or status of one or more work order operations.

sWorkOrdersUpdated(int, bool);

This slot tells other open windows that this script has updated the definition or status of one or more work orders.

sIdleTimeout();

This slot should rarely or never be called by a script. It is used by the application to inform the user that s/he has been idle for a long time and asks if the user wants to exit the application.

sFocusChanged(QWidget* old, QWidget* now);

This slot should rarely or never be called by a script. It indicates to the application that the user's focus has changed from one window to another.

Many of these slots "tell other windows" something. This means that any windows that are open and listening for certain corresponding signals listed below will refresh themselves. A script should only call these slots if you want to inform other open windows of changes made by the script itself. These slots do not save any data to the database and scripts are not required to call them. Any script that calls these slots should do so after committing changes to the database. A significant limitation of these slots is that they can only inform other windows opened by the same instance of the xTuple ERP application, not windows opened by other users whether or not they are running on the same host or workstation.

Signals

tick();

This signal is emitted on a regular basis, usually every minute or so. Scripts that want to repeat an action at a regular interval can connect to this signal. This is often used by Automatically Update check boxes.

checksUpdated(int, int, bool);

These signals are connected by the xTuple ERP application to the slots described above. Every time a window anywhere in the application calls one of those slots the application emits one of these signals. Scripts can connect these signals to functions in the script or to methods of the windows being scripted to cause certain activity whenever another part of the application reports a data change.

assortmentsUpdated(int, bool);

bankAdjustmentsUpdated(int, bool);

bbomsUpdated(int, bool);

billingSelectionUpdated(int, int);

bomsUpdated(int, bool);

boosUpdated(int, bool);

budgetsUpdated(int, bool);

cashReceiptsUpdated(int, bool);

configureGLUpdated();

creditMemosUpdated();

crmAccountsUpdated(int);

customersUpdated(int, bool);

glSeriesUpdated();

invoicesUpdated(int, bool);

itemGroupsUpdated(int, bool);

itemsUpdated(int, bool);

itemsitesUpdated();

paymentsUpdated(int, int, bool);

projectsUpdated(int);

prospectsUpdated();

purchaseOrderReceiptsUpdated();

purchaseOrdersUpdated(int, bool);

purchaseRequestsUpdated();

qohChanged(int, bool);

quotesUpdated(int, bool);

reportsChanged(int, bool);

returnAuthorizationsUpdated();

salesOrdersUpdated(int, bool);

standardPeriodsUpdated();

taxAuthsUpdated(int);

transferOrdersUpdated(int);

vendorsUpdated();

vouchersUpdated();

warehousesUpdated();

workCentersUpdated();

workOrderMaterialsUpdated(int, int, bool);

workOrderOperationsUpdated(int, int, bool);

workOrdersUpdated(int, bool);

Object mywindow

The mywindow object is the object representing the screen that the script is being run against. This can either be a QDialog or QMainWindow, depending on the specific screen. Often the difference between the two is of little concern but it is good to know, as some of the available methods will be different. Sometimes, as well, the way you implement your scripts could be different, as Dialogs are modal and do not allow other non-modal windows to be opened at the same time.

For a list of available properties, signals, and slots, please see the Qt reference documentation for QDialog and QMainWindow.

Object toolbox

The toolbox object is a collection of miscellaneous functions that xTuple has found useful for writing scripts. The toolbox is sure to grow over time. Here is the set of features available in xTuple release 3.1:

QObject * executeQuery(const QString & query, const ParameterList & params);

This method takes MetaSQL and a set of parameter name/value pairs, executes the resulting query against the database server, and returns that query as a ScriptQuery object.

QObject * widgetGetLayout(QWidget * w);

If you have a widget that you have added to a form or that you have retreived from a form, this method gives you the layout object that contains that widget. Then you can use one of the methods below to place another widget on the window near a widget you choose.

void layoutBoxInsertWidget(QObject *, int index, QWidget *, int stretch = 0, int alignment = 0);

These let you add new widgets to an existing form so that they are positioned appropriately and respond properly to window size changes.

void layoutGridAddWidget(QObject *, QWidget *, int row, int column, int alignment = 0);

void layoutGridAddWidget(QObject *, QWidget *, int fromRow, int fromColumn, int rowSpan, int columnSpan, int alignment = 0);

void layoutStackedInsertWidget(QObject *, int index, QWidget *);

QObject * menuAddAction(QObject * menu, const QString & text);

This lets you add an item to a menu with a particular name. The returned object is the action that will be taken when the user selects this new menu item. The script must populate the properties of this action to ensure the desired result.

QObject * menuAddMenu(QObject * menu, const QString & text, const QString & name = QString());

This lets you add a submenu to an existing menu.

QWidget * tabWidget(QWidget * tab, int idx);

This method lets you retrieve a particular tab from a tab widget. The idx is the integer associated with the desired tab.

int tabInsertTab(QWidget * tab, int idx, QWidget * page, const QString & text);

This method lets you add a new tab to an existing tab widget in position idx.

void tabRemoveTab(QWidget * tab, int idx);

This method lets you remove a tab from an existing tab widget.

void tabSetTabText(QWidget * tab, int idx, const QString & text);

This method lets you change the label of a particular tab in a tab widget.

QString tabtabText(QWidget * tab, int idx);

Thie method lets you retrieve the label of a particular tab in a tab widget.

QWidget * createWidget(const QString & className, QWidget * parent = 0, const QString & name = QString());

This method lets you create a new widget. The first parameter is the name of the desired widget's class (e.g. QSpinBox or ContactCluster). The second parameter is the window or dialog that should take responsibility for destroying the widget (usually this). The third parameter is the name of the widget (this is not the label visible to the user, but rather a string that can be used later to find the widget again if the QWidget* variable gets lost).

QObject * createLayout(const QString & className, QWidget * parent, const QString & name = QString());

This method lets you create a new layout. The first parameter is the name of the desired layout's class (e.g. QGridLayout or QHBoxLayout). The second parameter is the widget that layout is to be applied. The third parameter is the name of the layout (this is not the label visible to the user, but rather a string that can be used later to find the widget again if the QObject* variable gets lost).

QWidget * loadUi(const QString & screenName, QWidget * parent = 0);

This method lets you load a UI file from the database. The first parameter is the name of the screen as you have specified it in the Screen window of the application. The second parameter is the window or dialog that should take responsibility for destroying the widget (usually this).

QWidget * lastWindow() const;

bool printReport(const QString & name, const ParameterList & params);

This takes a report name and a set of parameter name/value pairs and causes the named report to print.

int messageBox(const QString & type, QWidget * parent, const QString & title, const QString & text, int buttons = 0x00000400, int defaultButton = 0x00000000);

This is a convenience function to create a message dialog. The first parameter is either "critical", "information", "question", or "warning" and controls the icon that will appear in the dialog. The second parameter is the parent window (usually this) and controls where the dialog box will appear. The third parameter lets the script set the title bar of the message dialog, while the fourth parameter sets the main content text displayed inside the dialog. The buttons and defaultButton parameters are describe further in the QMessageBox documentation; in general they let you choose from among a set of pre-defined buttons to display and select which one is the default button. This method returns the value of the button which the user clicked.

bool coreDisconnect(QObject * sender, const QString & signal, QObject * receiver, const QString & method);

This method allows the user to disconnect a signal/slot that was created by the core application. Qt Scripting disconnect can only disconnect connections made in Qt Script. This can be used when in those cases where the Qt Script disconnect can not be used.

Object ScriptQuery

The ScriptQuery class is used to manipulate database queries from xTuple ERP application scripts. You create and execute a query using the toolbox object's executeQuery() method. Once you have executed a query you can do any of the following things with it:

XSqlQuery query()

Get the XSqlQuery object from the ScriptQuery. XSqlQuery is an xTuple subclass of Qt's QSqlQuery class, so anything you can script with a QSqlQuery can be scripted with the value returned by query().

void setQuery(XSqlQuery query)

If you have an XSqlQuery object, perhaps from a prior call to query(), you can assign it to a ScriptQuery variable.

bool isActive()

These are wrappers around QSqlQuery object methods and are provided here to make it easy to use them. Consult the QSqlQuery documentation for more information.

bool isValid()

bool isForwardOnly()

bool isSelect()

bool first()

The script should call this when it needs to read the first result row from an executed query.

bool last()

The script should call this when it wants to skip over the intervening results and look at the last row returned by an executed query.

bool next()

The script should call this when it wants to retrieve the next available row from an executed query's result set. If the query has just been executed and first() has not yet been called, next() returns the first result row.

bool previous()

The script should call this if it needs to look backwards through the query result set.

int size()

This method returns the approximate number of rows in the query result set.

int numRowsAffected()

This returns the (approximate) number of rows modified by a query containing an update statement, removed by a query containing a delete statement, or added by a query containing an insert statement.

QScriptValue value(int)

Once the script has executed a query and positioned itself on a particular row (using first(), last(), next(), or previous()) you use value() to read the contents of a particular column. You can either refer to the column by its position in the select statement by passing an integer to value() or by its name in the query using the QString version of value().

QScriptValue value(const QString)

QVariantMap lastError()

You can learn about errors executing queries by looking at the lastError(). You can ask for the databaseText, driverText, text, number, type, and validity of the error. See the SampleScripts for examples of how lastError() can be used.

Most of the functions in the ScriptQuery class are wrappers around methods with the same name in the QSqlQuery class. You should read the QSqlQuery documentation for more information about them.

Script Debugging

It is difficult to debug an application script without a sophisticated development environment embedded in the xTuple ERP application. However there are some basic tools built in to the application and the Qt framework to help solve basic problems. The most important of these is debugging output. Unfortunately access to this output differs on the three major platforms. Conceptually they're the same, so here are the concepts. Qt has a function qDebug(). This is called any number of places in the Qt libraries and the xTuple ERP application for sending debugging messages to the developer. Qt uses this function to tell developers about unexpected and probably unintentional behavior by the application, such as application code asking a query object for a column that wasn't named in the query or syntax errors found while trying to execute a Qt Script. Script authors can use the sReportError() slot described above to send debugging output to the same place.

Qt applications are driven by the connections between signals and slots, so much of the time problems manifest themselves long after a script has been loaded. xTuple ERP does not install error traps for all script errors, just those that occur during the initial load of the script when its parent window is created. To help you debug script functions that are run as a result of signal/slot connections, use QtScript's try/catch mechanism: {{{!#javascript function sSave() {

}

mywindow.getChild("_save").clicked.connect(sSave); }}} When the user clicks on the button named _save, this code will cause a debugging message to be written that looks something like this:

exception in sSave: ReferenceError: executeQuery is not defined

Windows Script Debugging

On Microsoft Windows platforms you have to attach to the xTuple ERP application while it is running to see qDebug() and sReportError() output. For example you can start the xTuple ERP application and Visual Studio, then with Visual Studio select Tools -> Attach to Process... and select xtuple.exe from the list of Available Processes. In the Output window of Visual Studio, select "Show output from: Debug". Then try running your script from the main application and watch the Visual Studio Output window.

Macintosh Script Debugging

On the Macintosh you have two options. Either start the xTuple ERP application from a Terminal window or from the Finder. If you choose the first option, starting from Terminal, then you have to run the binary within the application package - .../xtuple.app/Contents/MacOS/xtuple. Now the debugging output will go to your Terminal window. If you use the open xtuple.app command then follow the same steps as debugging from the Finder.

If you open the application from the Finder or using open then run /Applications/Utilities/Console and watch that window for the debugging output.

Linux Script Debugging

On Linux you should run the xTuple ERP application from a Terminal or Shell window - .../xtuple and watch that Terminal or Shell window for the debugging output.

ScriptingGuide (last edited 2008-12-31 22:04:26 by ptyler)