Thursday, July 26, 2012

Parm-methods generator script

It may be boring to manualy create parm-methods for each an every class member variable, although there is an editor script for that. By the way, there is a bug-o-feature in AX 2012 editor, which does not allow you to select anything but an EDT for the variable type.

This is a script that auto-generates parm-methods for a class, based on its class declaration:
static void createParmMethod(Args _args)
{
    #AOT
   
    ClassName className = classStr(MyClass);  // <---------------- Write your class name here
   
    TreeNode classDeclarationTreeNode;
    TreeNode classTreeNode;
    TreeNode parmMethodNode;
   
    Source classDeclaration;
   
    System.Text.RegularExpressions.MatchCollection mcVariables;
    System.Text.RegularExpressions.Match mVariable;
    int matchCount;
    int matchIdx;
   
    System.Text.RegularExpressions.GroupCollection gcVariableDeclaration;
    System.Text.RegularExpressions.Group gVariableDeclarationPart;
   
    str variableType;
    str variableName;
   
    str pattern = ' (?<VarType>[a-zA-Z0-9_]+)[ ]+(?<VarName>[a-zA-Z0-9_]+);';   
   
    Source parmMethodBody;
   
    classTreeNode = TreeNode::findNode(strFmt(@"%1\%2", #ClassesPath, className));
   
    classDeclarationTreeNode = TreeNode::findNode(
        strFmt(@"%1\%2\ClassDeclaration",
        #ClassesPath,
        className));
   
    classDeclaration = classDeclarationTreeNode.AOTgetSource();
   
    mcVariables = System.Text.RegularExpressions.Regex::Matches(
        classDeclaration,
        pattern,
        System.Text.RegularExpressions.RegexOptions::Singleline);

    matchCount = CLRInterop::getAnyTypeForObject(mcVariables.get_Count());   
   
    for (matchIdx = 0; matchIdx < matchCount; matchIdx++)
    {
        mVariable = mcVariables.get_Item(matchIdx);
        gcVariableDeclaration = mVariable.get_Groups();
       
        gVariableDeclarationPart = gcVariableDeclaration.get_Item('VarType');
        variableType = gVariableDeclarationPart.get_Value();
       
        gVariableDeclarationPart = gcVariableDeclaration.get_Item('VarName');
        variableName = gVariableDeclarationPart.get_Value();
       
        parmMethodBody = new xppSource().parmMethod(variableType, variableName);
       
        parmMethodNode = classTreeNode.AOTadd('method1');
        parmMethodNode.AOTsetSource(parmMethodBody);
        classTreeNode.AOTsave();
    }
   
    classTreeNode.AOTcompile();
}

Tuesday, July 24, 2012

Regex looking for return calls within ttsbegin/ttscommit

Return calls should never appear within a ttsbegin/ttscommit pair. If they do, the application may eventually complain that


I have recently had to look for such an issue in a third-party code, and this is the job I used to looks for suspecious "returns", which did find one. Please note, that with the current regex pattern, there may be false positives, but in my case it would take more time to write a perfect pattern, than to manually look through those false positives:

 static void checkSourceForReturnsInTTS(Args _args)
{
    TreeNode treeNode;
    TreeNode sourceTreeNode;   
    TreeNodeIterator it;
    ProjectNode projectNode;
    TreeNodeTraverserSource traverser;
    Source source;
   
    System.Text.RegularExpressions.MatchCollection mcReturnsInTTS;  
    int matchCount;
    int matchIdx;   
   
    str pattern = 'ttsbegin.*[^a-z0-9_]return[^a-z0-9_].*ttscommit';
    str matchString;
   
    projectNode = SysTreeNode::getPrivateProject().AOTfindChild("MyProject");
    treeNode = projectNode.loadForInspection();
  
    traverser = new TreeNodeTraverserSource(treeNode);
    while (traverser.next())
    {
        sourceTreeNode = traverser.currentNode();
       
        source = sourceTreeNode.AOTgetSource();
        source = System.Text.RegularExpressions.Regex::Replace(
            source,
            '[/][*].*[*][/]',
            '',
            System.Text.RegularExpressions.RegexOptions::Singleline);
        source = System.Text.RegularExpressions.Regex::Replace(
            source,
            '[/]{2,}.*\n',
            '');       
       
        mcReturnsInTTS = System.Text.RegularExpressions.Regex::Matches(
            strLwr(source),
            pattern,
            System.Text.RegularExpressions.RegexOptions::Singleline);
       
        matchCount = CLRInterop::getAnyTypeForObject(mcReturnsInTTS.get_Count());
        if (matchCount > 0)
        {
            info(sourceTreeNode.treeNodePath());
        }
    }   
}

Wednesday, July 18, 2012

The X++ debugger does not open...

... although you turned off "Execute business operations in CIL" in the "Tools > Options".

Then there is probably a runAs(...) method somewhere down the call stack.

You can find such a runAs-call by setting a breakpoint in Visual Studio, attaching to the process and running your logic again. After the runAs-call is found, you can simply go replace it with a direct static method call.

For example:


After this change, the X++ debugger will stop where needed.

Remember - this change is only for debugging purpose. You should not do that in the production code.

Monday, July 16, 2012

If you forgot to fix DataSource property on a grid,...

... although its child controls are set up properly, then your form may behave in a weird way.

For example, we have a form with 2 linked datasources (SalesTable and SalesLine) and 2 grids accordingly:


The second grid, SalesLineGrid, has DataSource property set to SalesTable. That value was set by default at the moment the grid control was created. You can also see 3 controls in the SalesLineGrid, but their DataSource properties are set to SalesLine, so they are fine.

Now, if we open the form and switch between sales orders back and forth, the bottom grid with sales order lines will not work properly:


As you can see, the cursor position in the second grid changes in sync with that of the first grid, there are a lot of "empty" sales order lines, and, finally, the rendered sales order lines are actually wrong.

Going back to the form in the AOT and setting the SaleLineGrid datasource property to SalesLine will fix the issue:

Wednesday, July 4, 2012

AIF: Mind auto-generated Axd<DocumentName>.findCorrespondingAxBC method

Problem

You have just created a document service with the wizard. You need the document to contain an unbound value. So, you create a display method on one of the tables, and add a corresponding parm-method to Ax<TableName> class. Then you refresh the services, but the new field is not presented in the schema.

Solution

Check the new Axd<DocumentName> class. By default, it may have findCorrespondingAxBC method, overriding the base class method and always returning classnum(AxCommon). This is why your new parm-method in the Ax<TableName> class is ignored.

P. S. There are actually TODOs added to the auto-generated methods. It may be a good idea to clean up the TODOs first, and then continue with the service development.