Saturday, February 18, 2012

Code Snippets in AX 2012

I wanted to post a little follow-up on an article presented by my fellow blogger Brandon George. In his post XppSource Exposed: Inserting Code Snippets he explains to his colleague the mysterious source of the code snippets you can insert by typing keywords and pressing TAB in the Dynamics AX 2012 editor. Although the XppSource class in the AOT does contain the code for those snippets, it is not where they originate.

In this post I will show you how to add your own code snippets. Since they are X++ code, you can even pop up a dialog to ask for parameters.

First of all, what are we talking about? Well, in the Dynamics AX 2012 editor, you can type certain keywords that trigger code snippets. For example, open a code editor window and type "whileSelect" (without quotes, and a capital S! this is case sensitive).


after the whileSelect statement, press the TAB key. You will see the code snippet pop in.


There is the XppSource class in the AOT, which does contain the code for these snippets. However, the real trigger is the EditorScripts class, which has existed for quite a few versions in AX, but was previously only available by right-click in the code editor, and going to the "Scripts" menu. Note that the menus you get to see are somewhat intelligent, and you will get different templates if you have an editor window open for a job versus a method on a class. For example, if you add a new method on a class, you delete all the default code, and type "parm" and press TAB.


This will open a dialog to enter some parameters to create a new parm method.



There are templates for parm, main, etc. These are however not available when you are editing a Job, for example.

The EditorScripts class works by naming your methods after the "Scripts" menu path from the code editor. For example, the scripts menu has sub-menus named "addIns", "sendTo" and "template". You will notice there are a bunch of methods in the class that are prefixed with "addIns_", "sendTo_" and "template_". This is exactly how the scripts menu is built, it parses the method names and creates submenus. As you have probably guessed by now, by adding new methods with the "template_" prefix, you can add your own code snippets. Note that you can create new sub-menus as you want.

So let's create a new code snippet. We'll add a template method for a "find" method on a table. Now, the EditorScripts class knows a lot about the code window you have open. It knows the object you are editing code on, for example. So thinking about a find method... The EditorScript should know pretty much everything there is to know. If it knows what table we are editing on, it can find out what the primary key fields are. If it knows what the table is and what the primary key fields are, it can automatically create the code for the find method.
So, to follow the Dynamics AX practice of adding the code to the XppSource, we will start by adding a new method for "findMethod".
Now, this code is fairly elaborate and sort of takes away of the point of this article about code snippets, but I figured it's such a useful template to have that I would post the whole thing. I'm sure there's room for improvement, but this gets the job done, feel free to leave your comments and questions below, let me know if you are using this!.
Source findMethod(TableName _tableName, boolean _useReplacementKey = false)
{
    DictTable dictTable = new DictTable(tableName2id(_tableName));
    DictIndex dictIndex = new DictIndex(tableName2id(_tableName),
        (_useReplacementKey && dictTable.replacementKey() != 0) ? dictTable.replacementKey() : dictTable.primaryIndex());
    DictField dictField;
    int fieldIndex;

    if (dictIndex == null)
        throw error(strFmt("Couldn't find primary index for table %1", _tableName));

    // method signature returning the table
    source += strFmt('public static %1 find(', _tableName);

    // add the primary key fields as the parameters to the find method
    for (fieldIndex=1; fieldIndex<=dictIndex.numberOfFields(); fieldIndex++)
    {
        dictField = new DictField(dictTable.id(), dictIndex.field(fieldIndex));
        source += strFmt('%1 _%2, ', extendedTypeId2name(dictField.typeId()), dictField.name());
    }
    source += strFmt('boolean _update = false)\n');

    indentLevel = 0;
    this.beginBlock();

    // Declare the table
    source += this.indent() + strFmt('%1 %1;\n', _tableName);
    source += '\n';

    // Set update yes/no
    source += this.indent() + strFmt('%1.selectForUpdate(_update);\n', _tableName);

    // select firstonly
    source += this.indent() + strFmt('select firstOnly %1 where', _tableName);
    // add the primary key fields in the where clause
    for (fieldIndex=1; fieldIndex<=dictIndex.numberOfFields(); fieldIndex++)
    {
        dictField = new DictField(dictTable.id(), dictIndex.field(fieldIndex));
        source += '\n';
        source += this.indent() + strFmt('    %1%2.%3 == _%3', ((fieldIndex>1) ? '&& ' : ''), _tableName, dictField.name());
    }
    source += ';\n';

    source += '\n';

    // return the buffer
    source += this.indent() + strFmt('return %1;\n', _tableName);

    this.endBlock();

    return source;
}

So, next we need to create the method on the EditorScripts class that will call this source method and add it to the editor, based on the Scripts menu or the keyword assigned. Logically this goes under the "methods" sub-menu. So we will call our method "template_method_find". Note that the last portion of this name will be the keyword triggering the code snippet in the editor, and that the keyword is CASE SENSITIVE. Our new method will look like this:
public void template_method_find(Editor editor)
{
    xppSource       xppSource = new xppSource();
    Source          template;
    str             path = editor.path();
    TreeNode        treeNode = path ? TreeNode::findNode(path) : null;
    TableName       tableName;
    #TreeNodeSysNodeType

    if (treeNode)
    {
        treeNode = treeNode.AOTparent();
        if (treeNode && treeNode.treeNodeType().id() == #NT_MEMBERFUNCLIST)
        {
            treeNode = treeNode.AOTparent();
            if (treeNode && treeNode.treeNodeType().id() == #NT_DBTABLE)
            {
                tableName = treeNode.treeNodeName();
            }
        }
    }

    if (!tableName)
    {
        warning("Find method applies to tables only");
        return;
    }

    template = xppSource.findMethod(tableName);

    editor.insertLines(template);
}
Note that the whole treeNode section checks if this method is on a table. Optionally, you could add some code that prompts a dialog to ask for the table name in case the method is not on a table. Feel free to do so.

I tried to find a table without a find method, but I couldn't quite find one right away (and that's a good thing). But of course we can just try this anywhere. Let's open the CustTable table in the AOT, and add a new method. Remove all the existing code and type "find" and hit TAB.



Yay! Observant readers have noticed that the method supports primary key versus alternate key (if you're not sure what this means, check my article on RecId and Alternate Keys!).

6 comments:

  1. Hi,

    creating the prototype of the find method, there will be an error if the field is not the same as the EDT.
    So, instead of:
    source += strFmt('%1 _%1, ', dictField.name());
    it could be better to have:
    source += strFmt('%1 _%2, ', extendedTypeId2name(dictField.typeId()), dictField.name());

    Regards,
    Geoffrey

    ReplyDelete
    Replies
    1. Nice catch! Thanks for commenting. I updated the code. By pure chance, it worked when I tested it on a few examples.

      Delete
  2. Hello Joris

    Thanks for sharing. I always look forward to your posts on Dynamics Ax.

    Regards
    Diwakara Reddy

    ReplyDelete
  3. I did all of this, but it seem it is not refreshing the environment for the newly added method, what did i miss ?

    Copied the exact methods and putted in correct classes, cant hit it :(

    ReplyDelete
  4. ok got it, i restarted the Dynamics AOS and nowits working, excellent post this 1, really like it thanks dudes

    ReplyDelete
  5. thanks for great post...i am really very happy with this post

    ReplyDelete