Wednesday, June 26, 2013

How To: Pass a container via args.parm() functionality

In AX 2012 (and previous versions), there is something called an args() which is short for arguments. It primarily comes into play where an object (like a form) opens up another object.

From MSDN: http://msdn.microsoft.com/en-us/library/aa884376(v=AX.50).aspx
"The Args class is used to pass arguments such as a name, a caller, and parameters between application objects."

For example, if a form opens up from another form (think ListPage form to non-list page), information about that first form can be passed to the second form that is being opened. Usually this is data like what form the second form is being opened from, any special data parameters, etc.

Anyways, with that prefacing out of the way...

NOTE: Italics below indicate an update to the original post. I think it will make more sense to what the post is showing.

The Problem: There is a requirement to pass a container from one form to another form and we cannot use args.parmObject() functionality as its already taken. For this example, we are trying to pass a set of record Ids. While containers can have other values, we're just going to use recIds so I can show a few other features.

The Issue: There is no way to pass a container through other means. There are options to pass a string, enum, record set, static string value, or object instance, but no container. The parmObject instance is unavailable but args.parm() which only takes a string and is available.

The Resolution: Here is how it was solved:
  1. Take the container and convert it a string with commas separating the variables
  2. Pass the string to the args().parm() call in the first object
  3. Retrieve the string from args().parm() in the second object
  4. Convert the string to a container
For point 1, we need to use the con2str function. con2str(container c [, str sep] ).  For example, "con2Str(conRecIds,',')" would convert the container into a string using a comma as a separator.

For point 4, we need to use the str2con function. str2con(str_value [, str 10 _sep, boolean _convertNumericToInt64] ).  For an example, "str2con(strRecIds, ',', true)" would convert a string with commas as what distinguishes the individual components. The third parameter '_convertNumericToInt64' is really handy. It will take the string values and auto-convert them to an int64 (e.g. recId) when set to true.

Some code example:

IN FORM 1:
[Action: Highlighted a few records and clicked a button]
container conRecIds;
str       strRecIds;

tmpTable = dataSource_DS.getFirst(1); 

while (tmpTable.RecId != 0)
{
   conRecIds += int642str(tmpTable.omRecID);
   tmpTable = dataSource_DS.getNext();
}

strRecIds = con2Str(conRecIds, ',');
element.args().parm(strRecIds);

IN FORM 2:
container conRecIds;
str       strRecIds;

strRecIds = args.parm();
conRecIds = str2con(strRecIds, ',', true);

4 comments:

  1. This isn't passing a container technically, since a container can contain pretty much any value type, including nested containers.

    When you use con2str(...), it flattens the container and converts all data types to string. Also, str2con(...) strangely converts the objects back, so if you did for example:

    // Take note of the quotes
    container c = str2con(con2str([123, ["abc", "def"], "456", 3.14]));

    You will see `c = [123, "abc", "def", 456, "3.14"]`

    I'd say the best thing you could do for a container is pass a collection class (Array, Map, Set, etc) containing your value in args.parmObject(...) and then pull it out on the other end, or if you have FormA and FormB, you can create a method on FormA:
    container getContainer() {container c = ['abc', 'def']; return c;}

    Then on FormB, just call:
    if(formHasMethod(element.args().caller(), identifierstr(getContainer)))
    info(con2str(element.args().caller().getContainer()));

    ReplyDelete
  2. Kwitny,

    First and foremost, thanks for posting a comment! It spurred some good conversation below I think people will get interest in.

    To clarify, the post is about passing a container through the args parm functionality specifically, not the args framework. Specifically, when the args.parmObject() is already being used, but not the args.parm(). There are many examples of this being an issue in the system with existing forms linking to each other. But yes you are correct that is how the parmObject works. To pass something to args.Parm(), you should go the way I describe in the post.

    I will update my post to clarify the assumption that the scenario is about passing a container via args.parm() specifically because the args.parmObject is already allocated to another use.

    You are also correct that my scenario would not handle nested containers but then again, you would never have a nested container in the scenario I detailed above. I wanted to show a few features with con2str and the _convertNumericToInt64 parameters which are not possible with other methods. The way I specified works great for the scenario detailed out and is, IMO, the best approach.

    While we're on the topic though, I'd throw caution to nested containers. There is nothing wrong with them but they can be a beast to manage in very complex situations. And it can be a slippery slope in advanced functionality. To the original coder, it may be easy but to others trying to figure out what all the values are can be a pain.

    I would also challenge the assertion that your ways you detailed above are the best way for other scenarios. From experience, there are so many unique situations which ultimately change which define what the 'best' approach is.

    To note, I'm not a fan of the 'formHasMethod' check though. The functionality will allow the form to open from forms with or without a container. Yes it prevents errors but doesn't do anything for maintaining integrity of making sure the form opens with the correct data. The developer making a call from one form to that form would have to know that the source form may need to have a method 'getContainer' or the expected results might not work. I would rather determine the form or table record type where I have that form and then know to grab that method.

    Thanks for the comments and good to see you blogging more recently!
    ITB

    ReplyDelete
  3. Hey Justin I didn't even realize I was commenting on your blog when I made that comment earlier...I was just googling for something and stumbled across this post.

    To your caution about nested containers...I was referring mostly to scenarios where you would want to pass class.pack(), where there are often nested containers.

    And I prefer to use global variables and a singleton pattern ;) jk

    ReplyDelete
  4. Alex, Hope you're doing well! The AX community is small for sure. Great points you made in the last comment!

    ReplyDelete