+1-757-461-3022 x124

Web Services and the xTuple Desktop Client

Monday, May 13, 2013

While our development team is hard at work creating our very own REST API, I thought it was an appropriate time to highlight some of the Web service tools hidden in plain sight in xTuple's scripting toolbox on GitHub.

As an example, I have created a simple JSON object that looks like this, sample raw materials priced by day, and with a little scripting we can pull this data into our screen.

We begin by setting up our new display, using the Display Class introduced in the 3.7 series.

 

// sets the window title instead of the generic "display"
mywindow.setWindowTitle(qsTr("Web Service Example"));
// sets the label above the list 
mywindow.setListLabel(qsTr("Web Service Example..."));
// bind to the query button
var _query = mywindow.queryAction();
// set list not to populate altId's
mywindow.setUseAltId(false);
 // choose to enable query on start or not
mywindow.setQueryOnStartEnabled(false);
// hide the search box
mywindow.setSearchVisible(false);
 // disables the parameter widget, you can pass those parameters to web service queries but this text file won't accept them
mywindow.setParameterWidgetVisible(false);
// just to make the following easier to read
//var _pw = mywindow.parameterWidget();
//_pw.append(qsTr("Date"), "date", ParameterWidget.Date, mainwindow.dbDate());
// this runs through the defaults we specified making the defaulted filters visible
//_pw.applyDefaultFilterSet();
// bind to the list object
var _list = mywindow.list();
// add columns to list that comes along with our display class
_list.addColumn(qsTr("Date"), -1, Qt.AlignLeft, true, "date");
_list.addColumn(qsTr("Aluminum/LB"), -1, Qt.AlignLeft, true, "aluminum");
_list.addColumn(qsTr("Copper/LB"), -1, Qt.AlignLeft, true, "copper");
_list.addColumn(qsTr("Gold/LB"), -1, Qt.AlignLeft, true, "gold");
_list.addColumn(qsTr("Nickel/LB"), -1, Qt.AlignLeft, true, "nickel");
_list.addColumn(qsTr("Platinum/LB"), -1, Qt.AlignLeft, true, "platinum");
_list.addColumn(qsTr("Silver/LB"), -1, Qt.AlignLeft, true, "silver");
// the network access manager that will be making the network calls
var _netmgr = new QNetworkAccessManager(mywindow);

Nothing too drastic, just setting up the screen and adding the columns for the data we expect. The important line is the very last one instantiating the QNetworkAccessManager, which will do our network communication for us. Even though we aren't using it, I left the commented-out parameter widget code in here as a sample.

Now that we have the network manager at our disposal, it is time to write the code that will send our query to the remote API.

function sSendQuery()
{
  try
  {
    var url = new QUrl("http://pastebin.com/raw.php?i=65SHG0vZ" target="_blank"); //not a real api of course, but it works 

    // sample of how to get the date from our parameter widget
    //var date = mywindow.parameterWidget().parameters().date;

    // if we have it
    //if (date != undefined || date != "")
    //{
      //var query = new Object;
      //query.date = date;
      //url.setQueryItems(query); this adds the ?date= to the URL
    //}

    // prepare the request    
    var netrequest = new QNetworkRequest();
    netrequest.setUrl(url);

    // fetch content of provided url using our network manager
    _netmgr.get(netrequest);
  }
  catch (e) {
    print("sSendQuery - exception at line " + e.lineNumber + ": " + e);
  }
}

A little bit to take in here, but, just as in SQL, we are building our query string and then submitting our request to the server. Except, in this case, it is a Web server instead of a SQL server. Up next is what comes when we actually get an answer to this request, which is asynchronous and handled via a callback method.

function sGetResponse(netreply)
{
  try
  {
    if (netreply.error())
    {
      QMessageBox.warning(mywindow, qsTr("Network Error"),  qsTr("<p>The request for metals prices "
                             + "returned error %1<br>%2").arg(netreply.error()).arg(netreply.errorString()));
      return;
    }

    // get contents of response into a local variable
    var response = netreply.readAll();
    // we don't want to do anything with it if it is empty
    if (response == undefined || response == "") 
    {
      QMessageBox.warning(mywindow, qsTr("Network Error"),  qsTr("<p>The request for metals prices returned an empty response."));
      return;
    }
	
    // try and parse the JSON object into a javascript array
    var metals = eval('(' + response + ')');
    	
    var counter = 0;
    // loop over properties of array
    for (var prop in metals)
    {
        if (metals.hasOwnProperty(prop)) // javascript eccentricity to ensure we're only iterating properties directly in this array, not ones coming from the prototype chain or children
	{   // for each row returned we add one to the _list
	    var row = new XTreeWidgetItem(_list, counter, metals[prop].date, metals[prop].aluminum, 
			metals[prop].gold, metals[prop].copper, metals[prop].nickel, metals[prop].platinum,
			metals[prop].silver);
	    counter++;
        }
    }
  }
  catch (e)
  {
    print("sGetResponse - exception at line " + e.lineNumber + ": " + e);
  }
}
//disconnect query button from the sFillList() slot since we aren't using a metasql query
toolbox.coreDisconnect(_query,"triggered()",mywindow,"sFillList()");
//these two are imperative to the entire operation, the first one links the callback method to the repsonse from the webserver
//the second line links the query button on the screen to our code which makes the request
_netmgr.finished.connect(sGetResponse);
_query.triggered.connect(sSendQuery);

There is one more step. In order to use this in your client, you need an initMenu to add the menu entry. Sample one below. Save the above code in the database as webServiceExample, the code below as initMenu, and the entry appears under System > Utilities as Metal Prices.

var utilsMenu	= mainwindow.findChild("menu.sys.utilities");
var action = utilsMenu.addAction(qsTr("Metal Prices"), mainwindow);
action.objectName = "sys.metalPrices";
action.triggered.connect(metals);

function metals()
{
  toolbox.newDisplay("webServiceExample");
}

When you are finished, you should have a display that looks like this:

This isn't so scary. Hopefully, this will give you an idea of the kind of things you can do using the network access manager. I highly recommend reading through the QNetworkAccessManager documentation to learn some of the other things you can do.

David Beauchamp

Professional Services Team
The primary directive of the Professional Services Team — made up of xTuple staff and the xTuple Partner global community — is to ensure experts are available to assist in the execution of your new system using the established xTuple implementation methodologies and to optimize the benefits of your new enterprise application.