EditorScripts class

In Dynamics Ax we can automate frequent task. When we create a new table, then according best practices, find and exist method should be added to the table method. In this post ,we will demonstrate to automate this task. We will demo how to create a template for creating find and exist methods automatically based on the given tables primary or replacement key. This template will only available for table objects.

When we use right mouse click in the Ax code editor, we see the menu appearing with the template for the exists and find method.  

 There are two classes involved for this functionality: xppSource and EditorScripts. 

 

 

1. xppSource. In this class we create the logic for the find and exist method definition for the template. The template itself will be build in the EditorScripts class.

There are two methods created one for the exists method and another for the find method. Logic to generate key field values used by both methods are build in a separate method (getContainerValues). 

  

 

 

 

 

 

 

 

 

 

 

 

 

Logic for the Exists method definition (method foxExistMethod):

//FOX_EditorScript 8-Apr-2017 created by Admin
Source foxExistMethod(TableName  _tableName)
{
    FieldName           fieldName;
    IdentifierName      edtType,
                        varName;
    Counter             i,
                        j = 1;
    Source              sourceParm,
                        sourceText,
                        sourceWhere; 

    TableName  tableName = strLwr(subStr(_tableName ,1 ,1)) +
                        subStr(_tableName, 2, strLen(_tableName) -1); 

    container  fields              = this.getContainerValues(_tableName);
 

    for (i = 1; i <= conLen(fields); i = i + 3)
    {
        if (i > 1)
        {
            indentLevel  = 12;
            sourceParm  += #newLine+this.indent()+",";
            sourceText  += #newLine+this.indent()+"&&";
            sourceWhere  += #newLine+this.indent()+" && ";
        }

        fieldName       = conPeek(fields, i);
        varName         = conPeek(fields, i+1);
        edtType         = conPeek(fields, i+2);
        sourceParm      += strFmt(" %1  %2",edtType,varName);
        sourceText      += strFmt(" %1 ",varName);
        sourceWhere     += strFmt("%1.%2 == %3",tableName, fieldName, 
	varName);
    }

    source += strFmt("static boolean exist(")+sourceParm+")";
    source += #newLine+"{"+#newLine;

    indentLevel  = 4;
    source += this.indent()+"return ";
    source += "("+sourceText;

    indentLevel  = 12;
    source += #newLine+this.indent()+ 
	strFmt("&& (select firstonly RecId from %1", tableName);

    indentLevel  = 20;
    source += #newLine+this.indent()+ strFmt("where ")+sourceWhere+').RecId);';

    indentLevel = 0;
    source += #newLine+this.indent()+"}"; 

    return source;
}

 

 

 

 

 

Logic for the find method definition (method foxFindMethod):

//FOX_EditorScript 8-Apr-2017 created by Admin
Source foxFindMethod(TableName  _tableName)
{
    container           fields;
    Counter             i,
                        j = 1;
    FieldName           fieldName;
    IdentifierName      edtType,
                        varName;
    Source              sourceWhere,
                        check; 

    TableName  tableName = strLwr(subStr(_tableName ,1 ,1)) +
                        subStr(_tableName, 2, strLen(_tableName) -1);
 
    fields              = this.getContainerValues(_tableName);
    source              += strFmt("public static %1 find(", _tableName);

    for (i = 1; i <= conLen(fields); i = i + 3)
    {
        if (i > 1)
        {
           indentLevel  = 16;
           source       += #newline+this.indent();
           sourceWhere  += #newLine+this.indent()+" && ";
           check        += " && ";
        }

        fieldName       = conPeek(fields, i);
        varName         = conPeek(fields, i+1);
        edtType         = conPeek(fields, i+2);
        source          += strFmt(" %1  %2,",edtType,varName);
        sourceWhere     += strFmt("%1.%2 == %3",tableName, fieldName, 
				varName);
        check           += varName;
    }

    source += #newLine+this.indent()+
		strFmt(" boolean _forUpdate = false)",
		edtType, varName);

    indentLevel = 4;
    source += #newLine+"{";
    source += #newLine+this.indent()+strFmt("%1  %2;", 
		_tableName, tableName);
    source += #newLine;
    source += #newLine+this.indent()+strFmt("if (%1)", check);
    source += #newLine+this.indent()+"{";

    indentLevel = 8;
    source += #newLine+this.indent()+strFmt("if (_forUpdate)");
    source += #newLine+this.indent()+"{";

    indentLevel = 12;
    source += #newLine+this.indent()+
	strFmt("%1.selectForUpdate(_forUpdate);",tableName);

    indentLevel = 8;
    source += #newLine+this.indent()+"}";

    source += #newLine;
    source += #newLine+this.indent()+strFmt("select firstOnly %1",tableName);

    indentLevel = 12;
    source += #newLine+this.indent()+strFmt("where ")+sourceWhere+';';

    indentLevel = 4;
    source += #newLine+this.indent()+"}";
    source += #newLine; 

    source += #newLine+this.indent()+strFmt("return %1;",tableName);
    source += #newLine;
    source += '}';

    return source;
}

In both method we see a call to the ‘getContainerValues’ method. This method returns a container with the following information about the table key fields: EDT of the field, field name of the table and a variable name of the field name:

public container getContainerValues(TableName _tableName)
{
    container           fields;
    Counter             i, j = 1;
    DictEnum            dictEnum;
    DictField           dictField;
    DictType            dictType;
    FieldName           fieldName;
    int                 fieldCount;
    IdentifierName      edtType, varName; 

    DictTable  dictTable = new DictTable(tableName2id(_tableName));

    DictIndex  dictIndex = dictTable.indexObject(
                                    dictTable.replacementKey() ?
                                    dictTable.replacementKey() :
                                    dictTable.primaryIndex()); 

    if (dictIndex)
    {
        fieldCount = dictIndex.numberOfFields(); 

        for (i = 1; i <= fieldCount; i++)
        {
            dictField = new dictField(dictTable.id(), dictIndex.field(i));
            fieldName = dictField.name();
            varName = "_"+strLwr(subStr(fieldName,1,1)) +
                subStr(fieldName,2,strLen(fieldName)-1);

            if (dictField.typeId())
            {
                dictType = new DictType(dictField.typeId());
                edtType  = dictType.name();
            }
            else if (dictField.enumId())
            {
                dictEnum = new DictEnum(dictField.enumId());
                edtType  = dictEnum.name();
            }
            else
            {
                throw error(
                    strfmt("Field name '%1' does not exist",
                            fieldName));
            }

            fields = conIns(fields, j       , fieldName);
            fields = conIns(fields, j + 1   , varName);
            fields = conIns(fields, j + 2   , edtType);
            j = i + 3;
        }
    }

    return fields;
}

2. Editor Script class. In the editorScripts class, the definitions created in the xppSource class will be linked to a template, for both exists and find method definition: 

Logic structure looks the same for both, only the template (source) method call to the xppSource class source is different as template for the exists method is linked to foxExistsmethod in the xppSource and foxFindMethod for the find. As parameter for the template the table name is given. In the code snippet we can see how this is done. 

//FOX_EditorScript 8-Apr-2017 created by Admin
void foxTemplate_existMethod(Editor _editor)
{
    TreeNode  treeNode;
    xppSource source;
    Source    template; 

    TreeNode = EditorScripts::getApplObjectNode(_editor); 

    if (!treeNode)
    {
        return;
    } 

    _editor.gotoLine(1); 

    source      = new xppSource();
    template = source.foxExistMethod(TreeNode.AOTname());
    _editor.insertLines(template);
}

 To make the template available in the menu the following modifications needs to be done in the ‘isApplicableMethod’ of the editorScripts class. 

Actually what happens here is that we only would like to make the template available if user is in code editor of a table object and for example not in a job. 

// FOX_EditorScript - start  8-Apr-2017 created by Admin
        case methodStr(EditorScripts, foxTemplate_findMethod):
        case methodStr(EditorScripts, foxTemplate_ExistMethod):

            return (_aotNode &&
            _aotNode.treeNodeType().id() == #NT_DBTABLE);
       // FOX_EditorScript - end  8-Apr-2017 created by Admin

To test template, we create a table. In this example the primary key has two fields. 

Now we create a new method:When right mouse click, the menu appears. Via option scripts we see both templates.

When we select exists we see that exists methods will be generated with both key fields:

Generated exists method:

The generated find method: