SBM ModScript, Part 7 - REST Call Into ModScript

1 Likes
over 2 years ago

Sometimes, we want more information on a custom form, but we can't figure out how to get it. The answer might be a call to SBM JSON API using a REST widget. However, if you just can't seem to find a non-scripty way to get the data you want, consider invoking ModScript from the form. I have put together a sample application based on 11.3.1 (see 11.4 example below for updated version) which shows how you could do this. In my example, the process app has a Contact field, and I want to show more information about that contact and the company that they are part of. To do this, I wrote a ModScript that can be invoked via the Direct URL context. It requires that the contact ID be passed in, either as a URL parameter or as a JSON value in the body of the HTTP call. You wouldn't really need to provide flexibility like that, but here we are trying to give an example of both so that we can really see how to send data to ModScript in the Direct URL context.

 

The ModScript Script
var contactID = 0;
var method = "";
if ( !Shell.PostData().to_string().trim().empty() ){
	contactID = Shell.PostData().to_string().from_json()["contact"];
	method = "JSON";
}
else {
	contactID = Shell.Params()["contact"].to_string().to_int();
	method = "URLParam";
}

Here, you see that we first look at the Shell's "PostData()" value. This will hold the body of the HTTP request, as long as the request is a POST, the HTTP request "Content-Type" is "application/json", and the data length is less than our maximum allowed in the "ScriptPostDataMax" system setting (which defaults to 10 MB). So, first, the ModScript checks to see if we have any data in Shell.PostData(). If so, it casts the value to a string (most values in the Shell are Variant), then invoke from_json() on the string. Assuming that the JSON passed in was { "contact": 123 }, this should give us a Map with a single entry of "contact". So, the script immediately requests the value "contact" from the Map. Of course, the data sent in to ModScript could be quite complex, in which case you could catch the return value of "from_json()" and then process it.

If we did not get anything in Shell.PostData(), we then look in the Shell.Params(), which is a Dictionary of the URL parameters passed to this Direct URL call. In this case, we expect to find a value "contact" as a URL parameter. Values in Dictionaries are also Variant, so we need to cast it. Unfortunately, until 11.4, we do not have a direct Variant.to_int(), so we use Variant.to_string(), then use string.to_int() to finally get an integer.

Next, we want to read the Contact using the ID passed in:

var contactRec = Ext.CreateVarRecord( Ext.TableId("TS_CONTACTS") );
if ( contactID == 0 || !contactRec.Read( contactID ) ) {
	Ext.WriteStream("{}");
}

This is pretty straight forward. Create a VarRecord object for interacting with the "TS_CONTACTS" table. If we were not passed a contactID that we can read, we return an empty JSON object (the JavaScript consumer of this output expects JSON, so be sure to send valid JSON, even when there is an error).

else {
	var companyName = "";
	var companyAddress = "";
	var companyFld = contactRec.Fields().FindSysField( 200 ); // 200 is the syscode for the Contact's Company field.
	var companyID = Variant();
	if ( !companyFld.is_var_null() && fun( f, v ){ f.GetDbValue(v); return v; }( companyFld, companyID ) != 0 ) {
		var temp = Variant();
		companyFld.GetDisplayValue( temp );
		companyName = temp.to_string();
		var companyRec = Ext.CreateVarRecord( Ext.TableId( "TS_COMPANIES" ) );
		companyRec.Read( companyID );
		companyAddress = (companyRec.GetFieldValue("ADDRESS1")   ", "   companyRec.GetFieldValue("CITY")   ", "   companyRec.GetFieldValue("STATE")   " "   companyRec.GetFieldValue("ZIPCODE")).to_string();
	}

	Ext.WriteStream( [
                       "name" : contactRec.GetName(), 
                       "email" : contactRec.GetFieldValue("EMAIL").to_string(),
                       "company" : companyName,
                       "companyAddress" : companyAddress,
                       "method" : method
                      ].to_json() );
}

Here, we see a few important things. First, after calling VarFieldList.FindField() or VarFieldList.FindSysField(), you may have a null object (if the field could not be found). It is important to test for null using "is_var_null()". Next, you see a lambda which takes a Field and a Variant, it invokes Field.GetDbValue() and returns the value. This is because, until 11.4, ModScript did not have a version of Field.GetDbValue() that returned the value directly to the caller. Instead, I need a way to call Field.GetDbValue() after the check for is_var_null() but while still inside the "if" (or I could do nested "if" statements, I chose the lambda). Immediately after the lambda is declared, we invoke it with our Field and Variant, and let it give us the value that we can test.

Finally, we have the Company ID and we can read the Company to get the address. I put all of this together into a ModScript Map, then invoke "to_json()" to get a nice JSON string that JSON-encodes the embedded text values and formats the return value.

 

The JavaScript Script

On the JavaScript side, I have two examples to show both a POST with a JSON body and a GET with the data on the URL. Both put the results onto the custom form in the

location specified. The JavaScript writes an unordered list of information using the returned JSON. Keep in mind that in SBM, the jQuery object is called "jQuerySBM", not "$".

 
function processModScriptResult( data, updateLoc ) {
    console.log(data);
    var p = JSON.parse(data);
    $("#" updateLoc).html("<ul><li>"   p.name   "</li><li><a href=\"mailto:"   p.email   "\">"   p.email   "</a></li><li>"   p.company   "</li><li>"   p.companyAddress   "</li></ul>");
}

function invokeModScriptBody( val, updateLoc ) {
    $.ajax({
        contentType: 'application/json',
        accept: 'application/json',
        type: 'POST',
        data: JSON.stringify({ "contact": val }),
        url: "tmtrack.dll?ScriptPage&ScriptName=ModScriptDirectURL",
        success: function(data){ processModScriptResult(data, updateLoc); }
    });
}
 
function invokeModScriptParam( val, updateLoc ) {
    $.ajax({
        contentType: 'application/json',
        accept: 'application/json',
        type: 'GET',
        url: "tmtrack.dll?ScriptPage&ScriptName=ModScriptDirectURL&contact="   val,
        success: function(data){ processModScriptResult(data, updateLoc); }
    });
}

 

The Form

To pull it all together, I created a State form for my one and only workflow state. In the form's properties, I selected my custom JavaScript. I also ensured that "Include jQuery plugin" was checked. I added an HTML/Javascript widget, with the following HTML and Javascript&colon;

<script type="text/javascript">
  invokeModScriptParam( {Contact[ID, "ModScriptInsertLocation1" );
  invokeModScriptBody( {Contact[ID, "ModScriptInsertLocation2" );
</script>
<p id="ModScriptInsertLocation1"></p>
<p id="ModScriptInsertLocation2"></p>

This invokes my two examples, and injects a couple locations in the HTML for the JavaScript to write out the results.

 

What's New in 11.4

I wrote this application to work in 11.3.1. In 11.4, we could have simplified our script. Also, we could bind the JSON output directly to a REST Grid Widget on a form. I  have updated the sample application with the changes. A few notable differences:

  • Ext.SetContentType( "application/json" );
    • In 11.4, ModScript can set the declared Content-Type for the Direct-URL context. This is important as the REST Grid Widget does not allow you to bind to a request that returns a Content-Type of "text/html".
  • The JSON returned is now a JSON array so that the REST Grid Widget recognizes it.
  • Since we are using the REST Grid, we don't really need the HTML/JavaScript widget or the JavaScript file. However, I kept them so we can see all the different options. The JavaScript doesn't need to call JSON.parse(data), the new Content-Type of "application/JSON" indicates to the underlying engine that it should parse the JSON and give us an object. The script was changed to access index 0 of the array, as the JSON is now an array with our object inside.
  • To use the REST Grid Widget, I added an Endpoint that points to the ModScript call that we had been making via JavaScript. Be sure to edit the endpoint in Application Repository to point to your AE server. The REST Grid Widget can bind the Contact ID to the URL parameter.
  • Other cleanup in ModScript:
    • Invoke "Variant.to_int()" directly
    • Use "VarRecord.GetDbValueInt()" to get the field value as an integer
    • Setting our companyName string = Variant directly (no longer need to invoke Variant.to_string() when assigning a Variant value to a string).
    • 11.4 has VarRecord.GetFieldValueString(), but it still requires the string to be passed in rather than returned directly to the caller (this is so that it can return false if the field is not found). I added a function to the Field class in my script that will return the string value directly to the caller, which makes it easier to concatenate the address string. Only do this if you are SURE all the fields will be found:
      • def VarRecord::GetFieldValueString( s ) {
        	var ret = "";
        	this.GetFieldValueString( s, ret );
        	return ret;
        }

 

SBM ModScript - Table of Contents

Comment List
Anonymous
Parents Comment Children
No Data
Related Discussions
Recommended