How to render M3 API using Dojo DataGrid

Here is a solution to render the response of an Infor M3 API request into the Dojo DataGrid. This post is a continuation of my previous posts, How to call M3 API using the Dojo Toolkit, how to call M3 API with jQuery DataTables, and How to call an M3 Web Service using jQuery. Also, this is a solution for IBrix no longer supported.

<html>
<head>
<link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/dojo/1.9.1/dijit/themes/claro/claro.css">
<style type="text/css">
@import "//ajax.googleapis.com/ajax/libs/dojo/1.9.1/dojox/grid/resources/claroGrid.css";
#gridDiv {
 width: 1000px;
 height: 35em;
}
</style>
<script>dojoConfig = {parseOnLoad: true}</script>
<script src="//ajax.googleapis.com/ajax/libs/dojo/1.9.1/dojo/dojo.js"></script>
<script>
require(['dojo/_base/lang', 'dojox/grid/DataGrid', 'dojo/data/ItemFileWriteStore', 'dojo/dom', 'dojo/domReady!', 'dojo/request/xhr'],
function (lang, DataGrid, ItemFileWriteStore, Button, dom, xhr) {

    // make the M3 API request with dojo/request/xhr
    xhr('/m3api-rest/execute/CRS610MI/LstByNumber', {
       method: 'GET',
       withCredentials: true,
       user: 'Tschneider',
       password: '********',
       headers: {'Accept': 'application/json'},
       handleAs: 'json',
    }).then(render);

    // render the M3 API response with Dojo DataGrid
    function render(response) {
        // set up data store
        var data = {
            identifier: "id",
            items: []
        };
        var data_list = [];
        // loop thru the MIResponse
        for (var i in response.MIRecord) {
            // transform each MIRecord to an associative array accessible by key
            var record = {};
            response.MIRecord[i].NameValue.map(function (o) {
                record[o.Name] = o.Value;
            });
            // move each record to the data_list of the DataGrid
            data_list[i] = {
                col1: record['CONO'],
                col2: record['CUNO'],
                col3: record['CUNM'],
                col4: record['TOWN'],
                col5: record['PHNO'],
            };
        }
        for (var i = 0, l = data_list.length; i < data_list.length; i++) {
            data.items.push(lang.mixin({id: i + 1}, data_list[i % l]));
        }
        var store = new ItemFileWriteStore({data: data});

        // setup the DataGrid layout
        var layout = [[
            {'name': 'Company', 'field': 'col1', 'width': '75px'},
            {'name': 'Customer', 'field': 'col2', 'width': '150px'},
            {'name': 'Name', 'field': 'col3', 'width': '350px'},
            {'name': 'City', 'field': 'col4', 'width': '150px'},
            {'name': 'Telephone', 'field': 'col5', 'width': '150px'}
        ]];

        // create a new grid
        var grid = new DataGrid({
            id: 'grid',
            store: store,
            structure: layout,
            rowSelector: '20px'
        });

        // append the new grid to the div
        grid.placeAt("gridDiv");

        // call startup() to render the grid
        grid.startup();
    }
});
</script>
</head>
<body class="claro">
     <h1>Customers - CRS610</h1>
    <div id="gridDiv"></div>
</body>
</html>

Here is a screenshot of the result:

2

Note 1: The DataGrid will automatically provide client-side sorting and pagination, which in the case of M3 API is not suitable because we receive 100 records by default. So we have to implement our own server-side sorting and pagination, as well as handle positioning.

Note 2: I don’t like the solution too much as the data is copied seemingly four times in memory: in the response, in the data_grid, in the data, and in the store. There is most probably a solution to improve this memory footprint. I haven’t investigated yet.

Related articles

How to call M3 API using the Dojo Toolkit

Here is a solution to call Infor M3 API on the client-side using the Dojo Toolkit. This is a continuation of the previous posts, how to call M3 API with jQuery DataTables, and How to call an M3 Web Service using jQuery.

First, create an HTML file somewhere on the same domain as the M3 API REST endpoint as I explained in a previous post, for example:
1

Second, use the new dojo/request/xhr to make the HTTP Request to M3 API with REST/JSON:

<html>
<head>
<script src="//ajax.googleapis.com/ajax/libs/dojo/1.9.1/dojo/dojo.js"></script>
<script>
    require(['dojo/request/xhr'],
    function (xhr) {
        xhr('/m3api-rest/execute/CRS610MI/LstByNumber', {
        method: 'GET',
        withCredentials: true,
        user: 'Tschneider',
        password: '********',
        headers: {'Accept': 'application/json'},
        handleAs: 'json',
    }).then(function (response) {
        for (var i in response.MIRecord) {
            var record = {};
            response.MIRecord[i].NameValue.map(function (o) {
                record[o.Name] = o.Value; // transform each record to an associative array accessible by a key
            });
            console.log(record['CONO'] + ': ' + record['CUNO'] + ': ' + record['COR2'] + ': ' + record['WHLO'] + ': ' + record['TOWN']);
        }
    });
});
</script>
</head>
<body>
</body>
</html>

Then, open the webpage in a browser, in my case I used the Google Chrome JavaScript console (CTRL+SHIFT+J) to output the M3 API response:
2
That’s it! In a next post, I will show how to render the data using the Dojo DataGrid.

UPDATE 2013-07-10: To improve performance of client-side code for multiple M3 API requests, we can use the M3 API Pooling, the Generic pooling mechanism for client programs.

M3 API with jQuery DataTables

Here is a sample JavaScript code to render a list of results of an M3 API call using DataTables for jQuery. This is useful for web developers who need to easily display M3 data in a web page. This article builds on my previous post about How to call M3 API from JavaScript.

jQuery is a great JavaScript library for cross-browser compatibility, ease of programming, and added functionality. DataTables is great for rendering, pagination, sorting, and filtering of table data, all client-side; this will not be suitable for large amounts of data in which case a server-side positioning will be necessary by setting the necessary API input fields.

Here’s the source code:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
    <head>
        <title>M3 API with jQuery DataTables</title>
        <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
        <style type="text/css" media="screen">
            @import "http://datatables.net/media/css/site_jui.ccss";
            @import "http://datatables.net/release-datatables/media/css/demo_table_jui.css";
            @import "http://datatables.net/media/css/jui_themes/smoothness/jquery-ui-1.7.2.custom.css";
            body { padding: 10px }
            #container { width: 980px; }
        </style>
    </head>
    <body onload="run()">
        <h1>Customers</h1>
        
http://live.datatables.net/media/js/jquery.jshttp://datatables.net/download/build/jquery.dataTables.nightly.js // settings var userid = 'userid'; var password = 'password'; var program = 'CRS610MI'; var transaction = 'LstByNumber'; var maxrecs = 100; var returncols = 'CUNO,CUNM,CUA1,TFNO,STAT'; var inputFields = ''; // ex: CONO=1&CUNO=10001 // call the API and show the result function run() { try { // construct the URL var url = '/m3api-rest/execute/'+program+'/'+transaction+';maxrecs='+maxrecs+';returncols='+returncols+'?'+inputFields; // execute the request var xhr = new XMLHttpRequest(); xhr.open('GET', url, true, userid, password); xhr.setRequestHeader('Accept', 'application/json'); xhr.onreadystatechange = function () { // handle the response if (this.readyState == 4) { if (this.status == 200) { // get the JSON var jsonTxt = xhr.responseText; var response = eval('(' + jsonTxt + ')'); // show the metadata var table = document.getElementById('results'); var thead = table.tHead; if (thead === null) { thead = document.createElement('thead'); var tr = document.createElement('tr'); var metadata = response.Metadata.Field; for (var m in metadata) { var th = document.createElement('th'); th.appendChild(document.createTextNode(metadata[m]['@description'] + ' ' + metadata[m]['@name'])); tr.appendChild(th); } thead.appendChild(tr); table.appendChild(thead); // paint the table with jQuery DataTables $('#results').dataTable({ "bJQueryUI": true, "sPaginationType": "full_numbers" }); } // show the data var rows = response.MIRecord; for (var i in rows) { var fields = rows[i].NameValue; var values = []; for (var j in fields) { values.push(fields[j].Value); } $('#results').dataTable().fnAddData(values); } } else { alert(xhr.responseText); } } }; xhr.send(); } catch (ex) { alert(ex); } } </body> </html>

Here’s a screenshot of the result:

Related articles

How to call M3 API from JavaScript

Here is a solution to call M3 API from JavaScript, for example to call MMS200MI.GetItmBasic from an HTML page, using the solution described in a previous post on how to call M3 API with REST & JSON. This solution is interesting for easily integrating M3 into third-party web applications.

In this example, I will call the API program MMS200MI and the transaction GetItmBasic to get the details of a specified Item. This API accepts three input fields: Company (CONO), Item number (ITNO), and Language (LNCD).

First, let’s set the variables (replace the values with those that suit your environment):

var userid = 'm3userid';
var password = 'm3password';
var CONO = 100;
var ITNO = 'AN21R8';
var LNCD = 'GB';

Then, let’s construct the URL to make the REST call, and make sure to URI-encode the parameter values:

var url = '/m3api-rest/execute/MMS200MI/GetItmBasic?';
url += '&CONO=' + encodeURIComponent(CONO);
url += '&ITNO=' + encodeURIComponent(ITNO);
url += '&LNCD=' + encodeURIComponent(LNCD);

There are many solutions in JavaScript to make REST calls. I will use the built-in XMLHttpRequest object of modern browsers, but you can choose to use another library of your choice such as jQuery Ajax, Dojo.xhr, etc.

However, all modern browsers implement a same origin policy that restricts cross-domain requests to prevent malicious attacks on a page. Thus, our HTTP Request must be made on the same domain as the REST Service endpoint. For example, if our REST Service endpoint is located at http://hostname:33005/m3api-rest/ then your HTML page must be served with the same origin, i.e. same host and same port number. For example, I placed my HTML page in the mne/script/ folder of my Smart Office server to ensure the same origin policy. There are several workarounds to make cross-domain requests, for example JSONP makes the HTTP Request using a dynamic <script> tag; I haven’t tested such workarounds.

Then, let’s make the HTTP Request assuming your HTML page respects the same origin policy:

var xhr = new XMLHttpRequest();
xhr.open('GET', url, false, userid, password);
xhr.setRequestHeader('Accept', 'application/json');
xhr.send();

Remember, if your HTML page is not in the same domain as the REST service endpoint the browser will throw a JavaScript exception about cross-domain requests.

Then, let’s check if we received a response or not:

if (xhr.status == 200) {
   // success
} else {
   // error
}

The status codes returned could be 200 OK, 401 Unauthorized (incorrect userid/password), 500 Internal Error (invalid data rejected by M3), or something else.

If it’s 200 OK let’s parse the resulting JSON and show the result, otherwise let’s show the error message:

if (xhr.status == 200) {
   // success
   var jsonTxt = xhr.responseText;
   var response = eval('(' + jsonTxt + ')');
   var metadata = response.Metadata.Field;
   var fields = response.MIRecord.NameValue;
   for (var i in fields) {
      console.log(metadata[i]['@description'] + ' (' + fields[i].Name + ', ' + metadata[i]['@type'] + metadata[i]['@length'] + ') = ' + fields[i].Value);
   }
} else {
   // error
   console.log(xhr.responseText);
}

I use eval to parse the JSON but you can use another parser like Doug’s JSON, jQuery.parseJSON, dojo/json, or another parser of your choice.

Also, I use console.log to output the results to the JavaScript console of Google Chrome. Feel free to use any other technique of your choice.

Here is the full source code:

MITest-JS// <![CDATA[
   // set the input fields
   var userid = 'm3userid';
   var password = 'm3password';
   var CONO = 100;
   var ITNO = 'AN21R8';
   var LNCD = 'GB';
   // construct the URL
   var url = '/m3api-rest/execute/MMS200MI/GetItmBasic?';
   url += '&CONO=' + encodeURIComponent(CONO);
   url += '&ITNO=' + encodeURIComponent(ITNO);
   url += '&LNCD=' + encodeURIComponent(LNCD);
   // execute the request
   var xhr = new XMLHttpRequest();
   xhr.open('GET', url, false, userid, password);
   xhr.setRequestHeader('Accept', 'application/json');
   xhr.send();
   // handle the response
   if (xhr.status == 200) {
      // success
      var jsonTxt = xhr.responseText;
      var response = eval('(' + jsonTxt + ')');
      var metadata = response.Metadata.Field;
      var fields = response.MIRecord.NameValue;
      for (var i in fields) {
         console.log(metadata[i]['@description'] + ' (' + fields[i].Name + ', ' + metadata[i]['@type'] + metadata[i]['@length'] + ') = ' + fields[i].Value);
      }
   } else {
      // error
      console.log(xhr.responseText);
   }
// ]]>

Here is a screenshot of the result in Internet Explorer, Google Chrome, Firefox, and Safari of an HTML page with a form that I made:

That’s it!

UPDATE 2012-09-12:

You can access a field value directly by its field name with the following code:

var record = {};
response.MIRecord[i].NameValue.map(function(o){ record[o.Name] = o.Value; }); // transform each record to an associative array accessible by a key
record['ITNO']
record['ITDS']
record['STAT']
record['RESP']

Related articles

How to call Lawson Web Services from PHP

Here are examples to call Lawson Web Services from PHP for all three adapters: API, M3 Display Program (MDP), and SQL.

Which SOAP client ?

In the past, I used the external NuSOAP toolkit to make SOAP calls in PHP.

Now, PHP 5 comes built-in with SoapClient.

To determine which SOAP client your PHP server provides, use:

phpInfo();

It will show:

What’s the Endpoint ?

To determine the endpoint or the WSDL to our Lawson Web Service open the Lawson Web Services Runtime Management Page which you can launch from LifeCycle Manager or from Lawson Web Services Designer.

Select List, select the Service Context, and select the Web Service.

The bottom right corner will show the WSDL Address:

How’s the SOAP ?

I recommend using tools like Fiddler or soapUI to determine the exact structure of the SOAP Request and SOAP Response to call our Lawson Web Services.

Fiddler can intercept HTTP calls from most SOAP clients, for example from Lawson Web Services Designer, from Microsoft InfoPath, from PocketSOAP, or from Microsoft Visual C# Express, and we can use that SOAP Request and SOAP Response as a reference to write our PHP code:

Similarly, soapUI will create a sample SOAP Request from a WSDL, it will show the SOAP Response after the web service is executed, and we can use that SOAP Request and SOAP Response as a reference to write our PHP code:

M3 API adapter

Here is a sample PHP code to call a Lawson Web Service of type API.

The Web Service name is Customers, the operation name is LstByNumber. It calls the API CRS610MI.LstByNumber. It accepts Company and CustomerNumber as input fields; note the suffix Item in LstByNumberItem for the collection of input fields. It returns CustomerNumber and CustomerName as output fields; note the suffix ResponseItem in LstByNumberResponseItem for the collection of output fields.

<?php
	try {
		$client = new SoapClient("http://hostname:10000/LWS_DEV/svc/Customers.wsdl",
		array(
			'login'=>'M3SRVADM',
			'password'=>'*******'
		)
		);
		$response = $client->LstByNumber(array("LstByNumberItem"=>array(
			"Company"=>"001",
			"CustomerNumber"=>"00100001"
		)));
		foreach ($response->LstByNumberResponseItem as $item) {
			print($item->CustomerNumber." ".$item->CustomerName."\n");
		}
	} catch (Exception $e) {
		echo 'Message: ' .$e->getMessage();
	}
?>

M3 Display Program adapter

Here is a sample PHP code to call a Lawson Web Service of type M3 Display Program (MDP).

The Web Service name is Customers, the operation name is GetName. It works in CRS610/A/E, it accepts W1CUNO as an input field, and returns WRCUNM as an output field.

<?php
	try {
		$client = new SoapClient("http://hostname:10000/LWS_DEV/svc/Customers.wsdl",
		array(
			'login'=>'M3SRVADM',
			'password'=>'*******'
		)
		);
		$response = $client->GetName(array("CRS610"=>array(
			"W1CUNO"=>"0010001"
		)));
		print($response->CRS610->WRCUNM);
	} catch (Exception $e) {
		echo 'Message: ' .$e->getMessage();
	}
?>

SQL adapter

Here is a sample PHP code to call a Lawson Web Service of type SQL (JDBC).

The Web Service name is Customers, the operation name is Search. It works by doing a SELECT FROM WHERE on OCUSMA, it accepts CustomerName as an input field. And it returns OKCUNO and OKCUNM as output fields; note the new1Collection and new1Item automatically generated.

<?php
	try {
		$client = new SoapClient("http://hostname:10000/LWS_DEV/svc/Customers.wsdl",
		array(
			'login'=>'M3SRVADM',
			'password'=>'******'
		)
		);
		$response = $client->Search(array("CustomerName"=>"%ARMY%"));
		foreach ($response->new1Collection->new1Item as $item) {
			print($item->OKCUNO.", ".$item->OKCUNM."\n");
		}
	} catch (Exception $e) {
		echo 'Message: ' .$e->getMessage();
	}
?>

That’s it!

Related articles

How to call M3 API from .NET

Here is an example to call M3 API from .NET in C#.

Background

To call M3 API in .NET there are several options: 1) we can use Interop to wrap the COM unmanaged library, 2) we can use netmodules which were introduced in the M3 API Toolkit version 9.0.1.1, or 3) we can use the native .NET managed library which were introduced in the M3 API Toolkit version 9.0.3.0. I suggest the latter option.

Example

  1. Download and install the M3 API Toolkit version 9.0.3.0 or later.
  2. That version includes the .NET library MvxSockN.dll:
  3. You can use .NET Reflector to introspect the assembly:
  4. That version also includes documentation specifically for .NET:
  5. That version also includes C# examples:
  6. If you are using Microsoft Visual C# Express, add a New Reference to the DLL:
  7. Then add the namespace Lawson.M3.MvxSock to the source code:
    using Lawson.M3.MvxSock;
  8. Then start using MvxSock with IntelliSense:
  9. Here’s my sample source code:
    using System;
    using Lawson.M3.MvxSock;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                SERVER_ID sid = new SERVER_ID();
                uint rc;
                rc = MvxSock.Connect(ref sid, "hostname", 6800, "userid", "*********", "CRS610MI", null);
                if (rc != 0)
                {
                    MvxSock.ShowLastError(ref sid, "Error no " + rc + "\n");
                    return;
                }
                rc = MvxSock.Access(ref sid, "LstByNumber");
                if (rc != 0)
                {
                    MvxSock.ShowLastError(ref sid, "Error no " + rc + "\n");
                    MvxSock.Close(ref sid);
                    return;
                }
                while (MvxSock.More(ref sid))
                {
                    Console.WriteLine(MvxSock.GetField(ref sid, "CUNO") + ", " + MvxSock.GetField(ref sid, "CUNM"));
                    MvxSock.Access(ref sid, null);
                }
                MvxSock.Close(ref sid);
            }
        }
    }
  10. Here’s a sample result of calling CRS610MI.LstByNumber:

That’s it!

Related articles

How to call M3 API with REST & JSON

With the Lawson Grid, M3 introduced a new REST endpoint for calling M3 API where the response is returned in XML or in JSON.

Background

Historically, software clients that called M3 API needed to use proprietary libraries, such as MvxSockJ.class for Java and MvxSockX_SVR.dll for Microsoft languages. Over the years, Intentia introduced Movex Web Services to call M3 API using the SOAP standard. Then, Lawson introduced the Grid for cloud computing. In the process, Lawson replaced IBM WebSphere Application Server by open source software, it also replaced the Core Web Services for Lawson Smart Office, and it added Jersey, an open source implementation of JAX-RS (JSR 311). The positive outcome is that now, software clients can call M3 API using REST/JSON.

Why does it matter?

REST is increasingly popular, and years ago it started supplanting SOAP for web services [1].

Also, JSON is becoming the de facto data-interchange format, nicknamed “The Fat-Free Alternative to XML” [2].

Most importantly, it’s now possible to call M3 API without needing the proprietary libraries, and without needing Lawson Web Services. Software clients can now make simple HTTP Requests to call M3 API. This makes it easier to call M3 API from third-party software (such as Tibco, Business Objects, Web Methods, etc.) and from previously unsupported or difficultly supported programming languages (such as JScript.NET, JavaScript, Ruby, PHP, etc.).

What about Lawson Web Services?

The new REST endpoint for M3 API doesn’t preclude using Lawson Web Services. Lawson Web Services is still necessary for calling M3 API with the SOAP protocol, and for using the unavoidable M3 Display Program adapter which cannot be found in any other Lawson product. Also, Lawson Web Services is still necessary for Lawson Smart Data Tool.

REST endpoint

To access the new REST endpoint for M3 API, open a Grid Information page, which is accessible from any participating Host in the Grid:

For example: http://ussplu123:20005/grid/info.html

At the bottom of the page is the Application Name M3-API-WS, and it has a Link to the Rest Service‘s WADL:

For example: http://ussplu123:20005/m3api-rest/application.wadl

This WADL gives us the resource path:

execute/{program}/{transaction}

Where {program} is the M3 API Program, like MNS150MI, CRS610MI, MMS200MI, etc. And where {transaction} is that API’s transaction, like GetBasicData, AddAddress, LstByNumber, etc.

Let’s try to call the API CRS610MI.LstByNumber. The URL in my example would be: http://ussplu123:20005/m3api-rest/execute/CRS610MI/LstByNumber

The server will challenge us for HTTP Basic Authentication, we need to provide a valid M3 userid and password:

And the result in XML is a collection of MIRecord elements with Name/Value pairs:

JSON

We can get the result in JSON by adding the HTTP Header field:

Accept: application/json

The result is:

REST GUI

RESTClient is a great REST graphical user interface which we can use to test M3 API; you can download it at http://code.google.com/p/rest-client/

Paste the URL, set the M3 userid and password in the Authentication tab, optionally set the Header to get the response in JSON, and click Go!

Conclusion

With the Lawson Grid, software clients can now natively call M3 API from third-party software, and from previously unsupported or difficultly supported programming languages, without the need for proprietary libraries, and without the need for Lawson Web Services.

Updates

Input parameters

UPDATE 2012-07-17: You can set input parameters in the URL, for example: http://hostname:port/m3api-rest/execute/MMS200MI/GetItmBasic?CONO=531&ITNO=ABCDEF . Also, you can hook RESTClient to use Fiddler as a proxy in Tools >;; Options.

Max number of records

UPDATE 2012-07-31: You can set the maximum number of records returned with the matrix parameter maxrecs, for example: http://hostname:port/m3api-rest/execute/CRS610MI/LstByNumber;maxrecs=20?CONO=1

Output fields

UPDATE 2012-08-02: You can set the output fields returned with the parameter returncols, for example: http://hostname:port/m3api-rest/execute/CRS610MI/LstByNumber;returncols=CUNO,CUNM,CUA1,STAT,PHNO

Related articles