File Class D365/AX7 CSV Export X++

Hello DAX DEV,
Here’s a simple class doing a CSV file Construction and Export in Dynamics 365
The EDTs FilePath, FileNameSave, FileNameOpen are Obsolete in AX7, So creating dialog fields to manage the file info is no longer an option.

There is a new class called File to replace file handling.

unnamed1
unnamed2
unnamed3

This is the whole code exporting custInvoice Info to a CSV.

Notice how easy is it to send the file to the User
Line 46: File::SendStringAsFileToUser(str _csvFileContent, str _filename);

//Begin: Felipe Nogueira - AXAPTAHUT , Dec 17th 2018
class FileExporter
{
commaStreamIo iO;
custInvoiceTrans custInvoiceTrans;
Filename filename;
public static void main(Args args)
{
FileExporter fileExporter = new FileExporter();
fileExporter.run();
}
void new()
{
iO = commaStreamIo::constructForWrite();
filename = "CustInvoiceExport.csv";
}
void run()
{
if (Box::yesNo("Do you wish to extract Customer Invoice info to a CSV file?",DialogButton::Yes))
{
container header = ["Invoice number",
"Invoice Date",
"Item Id",
"Price",
"Qty",
"Line Amount"];
iO.writeExp(header);
header = conNull();
while select custInvoiceTrans
order by InvoiceId,LineNum
{
container line = [custInvoiceTrans.InvoiceId,
custInvoiceTrans.InvoiceDate,
custInvoiceTrans.ItemId,
custInvoiceTrans.SalesPrice,
custInvoiceTrans.Qty,
custInvoiceTrans.LineAmount];
iO.writeExp(line);
}
System.IO.Stream stream = iO.getStream();
stream.Position = 0;
System.IO.StreamReader reader = new System.IO.StreamReader(stream);
str csvFileContent = reader.ReadToEnd();
File::SendStringAsFileToUser(csvFileContent, filename);
info(strFmt("CSV file %1 Sent to user",filename ));
}
}
}
//End: Felipe Nogueira - AXAPTAHUT , Dec 17th 2018
view raw FileExporter.cs hosted with ❤ by GitHub

Follow us and get exclusive AX DEV content weekly

 

Augmented classes on Tables [ExtensionOf]

Recently I posted a Trick to create a display method on sys Tables using overlaying. In further researches, due to necessity, I bumped into a new and more robust way of doing the same.

The purpose of this post is to explain a possibility to facilitate the manipulation of Table Methods on Augmented classes.

Augmented classes are necessarily  Final classes defined by the use of the Attribute ExtensionOf on its declaration, in my example I’m augmenting the Table ProjForeCastOnAcc.

//Begin: Felipe Nogueira - AXAPTAHUT , Apr 9th 2018
[ExtensionOf(tableStr(ProjForecastOnAcc))]
final class ProjForecastOnAcc_Extension
{
}
//End: Felipe Nogueira - AXAPTAHUT , Apr 9th 2018

This allows us to interact with the class as if it were the actual table, So we have access to fields, methods, and properties from the original table.

I created this new display method on it, notice that at line ‘8’  of the following code, I’m mentioning one field from the original table by using the reserved word “this”, although the current object is not the actual table. All fields, methods, and properties from the original table can be accessed this way as if we were in the actual object.

//Begin: Felipe Nogueira - AXAPTAHUT , Apr 9th 2018
[ExtensionOf(tableStr(ProjForecastOnAcc))]
final class ProjForecastOnAcc_Extension
{
[SysClientCacheDataMethodAttribute]
public display ProjGrantName grantName()
{
return ProjGrant::findByGrantId(this.ProjGrantId).GrantName;
}
}
//End: Felipe Nogueira - AXAPTAHUT , Apr 9th 2018

X++ Executing Table methods from Augmented classes

This feature allows us to access these new methods as if they were located in the original table wherever a buffer is created. The next code is a non-related Class making use of the new Display Method on the Augmented class, notice line 12 of the following code:

//Begin: Felipe Nogueira - AXAPTAHUT , Apr 9th 2018
class ProjForecastOnAcc_Ext_Test
{
static void main(args args)
{
ProjForecastOnAcc forecastOnAcc;
while select forecastOnAcc
where forecastOnAcc.ProjGrantId
{
info(strfmt("%1 - %2",
forecastOnAcc.ProjGrantId,
forecastOnAcc.grantName()));/*New method on ProjForecastOnAcc_Extension*/
}
}
}
//End: Felipe Nogueira - AXAPTAHUT , Apr 9th 2018

Form usage of display methods from Augmented classes

The way of mentioning the extended methods on Forms is still the same as in the old trick. You must have the correct DataSource, then insert the full name of the Augmented Class together with the desired display method name to the field’s property “Data Method”, like this:

  • DataSource: ProjForecastOnAcc
  • DataMethod: ProjForecastOnAcc_Extension::grantName

ExtensionOf1

If done correctly, this solution should work without any issues.

Follow us and get exclusive AX DEV content weekly

Hope it might be helpful,
Thanks,
Felipe Nogueira

 

Customizing Hierarchical Grid Common (Project WBS Grid) Dynamics 365

Hello DAX DEV,

I recently was given a task which the purpose was to disable some fields, buttons and change the calculation of the Project WBS Hierarchical Grid Control in Dynamics 365.

With the requirement of using Extensions and Overlaying and not customizing any SYS objects, the following topics explain a little bit about the necessity, its implications and how it turned out to be possible.

The WBS form Path is: Project management and accounting > Projects > All projects > PLAN (Button Group) > Work breakdown structure (Menu item button)

WBS Original

The highlighted control is not a regular Grid, it is a Hierarchical Grid Common. After debugging it a lot, I found out that the grid is almost entirely controlled by a JavaScript file mentioned on the properties of this Grid control:

Property box Original

Note issue#1: JS path property cannot be altered on this form nor on a form extension, It’ll always pop up this warning and cancel the operation:

errorJS

Note issue#2: It is impossible to overwrite the “Object” Property on a display menu item extension, It’ll always pop up this warning:

MenuItemDisplayError

Scenario difficulties:

  1. As mentioned, it is a requirement that all the changes are limited to new objects and/or extensions only, we cannot customize existing elements from MS Models.
  2. All Grid calculations happen on this JavaScript file, not on any X++ based Object.
  3. The Proj WBS Form and its extension do not allow any change to the property  “JavaScript Extension file” on the Hierarchical Grid Common  Issue#1.
  4. It is impossible to overwrite the Object Property of a display Menu Item Extension Issue#2.
  5. Javascript files are stored in the Application explorer as Resources and Microsoft did not release yet the functionalities of extensions or overlaying for Resources.

Solution :

  1. Duplicate the JS file,  with the same code but with a different name.
    • Further changes to the behavior of the Grid will be manipulated in this JS file.
  2. Duplicate the form & Create a new Menu Item Display for it.
    • In this new form duplicated from “ProjWorkBreakdownStructure” , find the Hierarchical Grid and Change its property “Javascript Extension Files” to your new JS resource name.
      • Issue 1, Only by duplicating the form AX will allow the user to alter the “Javascript Extension Files” property of the Hierarchical Grid Common control.
      • Issue 2, By default, the property object cannot be altered on a Display Menu Item extension, so a new menu item is needed to access the duplicated form.
  3. Overlay the Init Method of the original Sys Form
    • Create a new “FormHelper” class, and overlay the init Method from Form ProjWorkBreakdownStructure, then use X++ to get the args object from the Original Form execute the form methods run* and close, Then open the menuItem of the duplicated form and send the original args object to it.
      • (*) It is indispensable to execute the method Run before closing the original form, so AX won’t crash.
class ProjWorkBreakdownStructureFormHelper
{
/// <summary>
/// Method closes ProjWorkBreakdownStructure and opens the
/// custom form newProjWorkBreakdownStructure with the same args.Record()
/// </summary>
/// <param name="args"></param>
[PostHandlerFor(formStr(ProjWorkBreakdownStructure), formMethodStr(ProjWorkBreakdownStructure, init))]
public static void ProjWorkBreakdownStructure_Post_init(XppPrePostArgs _args)
{
Object formRun = _args.getThis();
Args args = formRun.args();
formRun.run();
formRun.close();
new MenuFunction(menuItemDisplayStr(newProjWorkBreakdownStructure),MenuItemType::Display).run(args);
}
}
  1. Alter the new JS file to perform the changes needed. In my case they were:
    • Removing 3 fields from the GRID: 
      • Around line 1300 of the JS file, there is a function called “getColumns” where the object “columnsArray” is located. This object can be manipulated and controls the Columns visibility and order

WBSCollumnsArray

    • Removing 2 Buttons from Grid’s Action pane:
      • Close to line 1500 of the JS file there is a function called  “_initToolbarContainer” that receives an object “container”, this container controls the buttons visibility and order.

wbsToolbars

    • Altering the Grid’s calculation on field changed: 
      • Approximately at line 525 of the JS file, there is a function called “cellValueChanged” that is executed whenever there’s a data modification on any field of the Grid, This function has a switch case with one separated case for each field. All the data calculation and grid responsiveness happen here, so any change in the logic needs to be done here. 

WBSCalculationJS1

      • Perform the change on the Grid’s calculation by manipulating the new JS file:
      • Some of the cases of the switch case trigger a calculation of values. To do the required modifications it was necessary to change those cases, comment some specific lines and call the newly created function “calcResourceNum” that unifies the calculation with the expected calculation rule.

WBSCalculationJS2

After altering the JS file, the behavior of the Grid was exactly as expected, the unnecessary fields and buttons were removed, and the new calculation rule was placed.

Developer important note – Minor Threat to MS HotFixes: 

There is a drawback to this solution, from my perspective it is currently insoluble, that is: Because it was impossible to manipulate the JS path property on the Form Extension Issue#1 and it is also impossible to alter the Object Property of a Display menu Item Issue#2, It was necessary to create a form duplicate and a PostHandler to catch the regular form Init method, close it and open the duplicate Form by code with the same arguments.

With this solution in place, if Microsoft releases a new hotfix that includes changes to this form, at first, the change will not take place automatically. It will only take place if the HF changes are also replicated manually to the duplicated form or after removing the old duplicate and creating a new duplicate with the same name, Doing step 2 of the solution again to catch up with the latest MS HotFix changes to this form.

Follow us and get exclusive AX DEV content weekly

Thank you for reading, I hope it had been helpful,
Felipe Nogueira

Display Method on Sys Tables Using Overlaying D365

Hello DAX DEV,

Before going to the trick I’ll explain a little bit about Overlaying and extensions on Ax Objects:

Intro About Extensions:

One of the great feature added to Microsoft Dynamics 365 is the concept of Overlaying and Extensions, available for most objects, like tables, forms, data types, menu items, queries and so forth.

To guarantee the integrity of the original code from Microsoft many AX objects can now have their behavior or properties overwritten by this new set of elements instead of manipulating the actual Sys Code.

It is still possible to customize original objects, but this is not a best practice, so much so that most D365 Development Administrators have disabled this possibility on DAX DEV servers, to force developers to manipulate Sys Objects only by extensions and overlaying.

Tables extensions are very useful to create structural changes, like altering field dataTypes, Labels, adding new fields, creating relationships, indexes, events interceptions and so on, but you cannot create or alter table methods on a table extension, so when this is required, the limitation presents a problem.

ProjTableExt
The solution consists on creating a new Static Class, that will work as source code for your Sys Table and here’s an example of a display method for table ProjBudget that will be used later on ProjBudget Form [Extension].

SOLUTION

THERE'S A NEW AND BETTER WAY OF DOING THIS. CHECK NOW ON AXAPTAHUT
 Augmented classes on Tables [ExtensionOf]
  1. First, you need to create a new Static class that will act as the extended source code for the table using the naming pattern TableName_Extension (trick: you can add project suffixes to the  class name but it is important that the class be static)
  2. Then add a public static display method receiving the table as a parameter.
    • static class ProjTable_Extension
      {
      [SysClientCacheDataMethodAttribute(true)]
      public static display DirMemo projValGroupId(ProjTable _projTable)
      {
      DirMemo ret;
      container conGroupId;
      ProjValProjCategorySetUp projValCategorySetup;
      while select GroupId from projValCategorySetup
      where projValCategorySetup.ProjId == _projTable.ProjId
      {
      conGroupId += projValCategorySetup.GroupId;
      }
      ret = con2Str(conGroupId,",");
      return ret;
      }
      }
  3. Then, on the Form or Form extension, that has that table, you’ll add the display method in a slightly different manner than usual. After creating field control where it necessary, you should set the correct “Data Source” property and here’s another trick, you should input an expression to the Data Method property mentioning the static class and the static display method you’ve created
    • Data Method Pattern: TableName_Extension::DisplayMethodName
    • Here’s the example:
      • DataSource: ProjTable
      • DataMethod: ProjTable_Extension::projValGroupId
    • DisplayonFormsExt

After performing those steps, your display method should work without any issues.

Hope it might be helpful,

 

Follow us and get exclusive AX DEV content weekly

Thanks,
Felipe Nogueira

Exam MB6-890: Dynamics AX Dev Intro [Dynamics 365 / Ax7] Usefull Info & links

Get ready for AX 7 Dev Intro Certification!

Hello DAX DEV,

I took the exam MB6-890 last year Jun 2017 and, in my opinion, if you are an experienced AX2012/09 developer,  You will need to study diligently some concepts that I will be mentioned here in this post. But I don’t think the exam is too complicated if you worked with the older versions of AX you’re probably familiar with at least 40%+ of the exam’s content, things like  X++ Code flow Read & write, Security Objects and some core falimiar transaction tables & classes did not change, and they will be mentioned, so it’s worth taking a look again.

If you are not familiar with the older versions and  you wish to take the exam, I’d advise you to take the Full MS course.

Although the most recognized tables & classes structures barelly changed in this new version, there are some major changes in the whole architecture of the ERP, many new tecnologies are involved. there’s a completelly different way of deploying and maintaining the development, might take a while to get used to, but in practice it is simple.

Considerable diferences when developing for AX2012 & AX07

First and most important change for us developers is the new environment [IDE], that is completelly on Visual Studio, with many new functionalities, with t new way of deploying and tranfering code between environments.

Changed & Extinct concepts: 3 concepts were completely revised.

  1. There are no more XPO’s files [Check ax07 Deploy]
  2. No more Morphx [Check Form Patterns]
  3. No more X++ Jobs(*) .

(*): If you want to do execute some specific code, you’ll need to create a class with a main method , place your code  in the main method and set the class as the default startup Object of your project and set your project as start up project of your solution, then run the solution, which will execute the class. [Check links: 1. Getting started   & 2. Debugging without a startup object]

New Concepts

Microsoft was very wise when building this new ERP,  All code was remastered in order to be in accordance with the most recent implementation norms & patterns. The objects extensions allow the developer to alter objects appearence & behavior by adding code to extension files, without changing the Original source file, In run time both the Original File and all the overlayering extentions will be read. This new architecture facilitates merges, HotFixes, MS updates and protects SYS code.

Important concepts  to study, (what I believe to be the most important):

  1. Models and Packages. (Deploy & Import processes, no more XPOs)
  2. Overlayering Extensions.
  3.  Form Patterns.

A good knowledge of these 3 topics will come handy not only for the exam but also to avoid problems with deployments once you’re developing. So invest some time to fully grasp the whole concept.

Apart from the MS online material, I studied other sources. Here are all 8 helpful links:

1 – Brief outlook of the exam.
2. MBS Parter Course. (Full MS course, A valid MS credential is needed)
3 – X++ Code Flow.
4 – Form Patterns.  (Usefull post to understand the new form design architecture)
5 – Customization overlayering extensions.
6 – Microsoft Doc About Models.
7 – Microsoft Doc About Projects.
8 – Packages, models, and projects. (Succinct and direct info)

AxaptaHut Note: Our purpose is to feed the community with good & usefull information, so if you have good info about this subject and you feel it is missing here, do not hesitate, SHARE WITH US!! let’s be collaborative, all AX community is thankfull.

Good luck to all of those who will take the exam and leave your comments below.
 

Follow us and get exclusive AX DEV content weekly

Thanks,
Felipe Nogueira.

Find Method Generator AX2012

Hello DAX DEV,

Here’s a tool to automatically create the Find method on tables based on the Primary Unique index, it appears in the context menu of the code editor in Ax 2012.

This is how it works, first, you have to make sure your table has a unique index:
carTable

newMth

Then you create a new method on the table, select all the text and execute the function FindByPrimaryIndex in the Context menu.

Script

This new tool will pop-up dialogs, as the images below, to each unique index in a loop in case there’s more than one until the developer chooses one of the indexes [By pressing “Yes” on the dialog].

If there are no unique indexes, or the user rejects all the possible indexes, the solution will propose to create the find by the RecId field.

1.Finddialog

2.Finddialog2

3.Finddialog3

I’ve generated the three possibilities of Find methods for this scenario and the results are:

find by index CarIdx_UniqueId

/// <summary>
/// Finds the specified record in the <c>CarTable</c> table.
/// </summary>
/// <param name="_carId">
/// The CarId of the <c>CarTable</c> record to find.
/// </param>
/// <param name="_forupdate">
/// A Boolean value that indicates whether to read the record for update; optional.
/// </param>
/// <param name="_concurrencyModel">
/// The table selection ConcurrencyModel; optional.
/// </param>
/// <returns>
/// A record in the <c>CarTable</c> table; otherwise, an empty record.
/// </returns>
// Begin : Axaptahut, DevTools
public static CarTable find(AccountNum _carId, boolean _forupdate = false,
ConcurrencyModel _concurrencyModel = ConcurrencyModel::Auto)
{
CarTable carTable;
carTable.selectForUpdate(_forupdate);
if (_forupdate && _concurrencyModel != ConcurrencyModel::Auto)
{
carTable.concurrencyModel(_concurrencyModel);
}
select firstonly carTable
where carTable.CarId == _carId;
return carTable;
}
// End: Axaptahut, DevTools
view raw Find.CS hosted with ❤ by GitHub

find by index CarIdx_UniqueCombination

/// <summary>
/// Finds the specified record in the <c>CarTable</c> table.
/// </summary>
/// <param name="_carId">
/// The CarId of the <c>CarTable</c> record to find.
/// </param>
/// <param name="_brand">
/// The Brand of the <c>CarTable</c> record to find.
/// </param>
/// <param name="_forupdate">
/// A Boolean value that indicates whether to read the record for update; optional.
/// </param>
/// <param name="_concurrencyModel">
/// The table selection ConcurrencyModel; optional.
/// </param>
/// <returns>
/// A record in the <c>CarTable</c> table; otherwise, an empty record.
/// </returns>
// Begin : Axaptahut, DevTools
public static CarTable find_Combination(AccountNum _carId,
EcoResAttributeTypeName _brand, boolean _forupdate = false,
ConcurrencyModel _concurrencyModel = ConcurrencyModel::Auto)
{
CarTable carTable;
carTable.selectForUpdate(_forupdate);
if (_forupdate && _concurrencyModel != ConcurrencyModel::Auto)
{
carTable.concurrencyModel(_concurrencyModel);
}
select firstonly carTable
where carTable.CarId == _carId
&& carTable.Brand == _brand;
return carTable;
}
// End: Axaptahut, DevTools

find by RecId

/// <summary>
/// Finds the specified record in the <c>CarTable</c> table.
/// </summary>
/// <param name="_recId">
/// The RecId of the <c>CarTable</c> record to find.
/// </param>
/// <param name="_forupdate">
/// A Boolean value that indicates whether to read the record for update; optional.
/// </param>
/// <param name="_concurrencyModel">
/// The table selection ConcurrencyModel; optional.
/// </param>
/// <returns>
/// A record in the <c>CarTable</c> table; otherwise, an empty record.
/// </returns>
// Begin : Axaptahut, DevTools
public static CarTable findRecId(RecId _recId, boolean _forupdate = false,
ConcurrencyModel _concurrencyModel = ConcurrencyModel::Auto)
{
CarTable carTable;
carTable.selectForUpdate(_forupdate);
if (_forupdate && _concurrencyModel != ConcurrencyModel::Auto)
{
carTable.concurrencyModel(_concurrencyModel);
}
select firstonly carTable
where carTable.RecId == _recId;
return carTable;
}
// End: Axaptahut, DevTools
view raw findRecId.cs hosted with ❤ by GitHub

To add this functionality the following method must be placed in the Editor Scripts class.

Editor Scripts Class: Updated Code for AX4 Compatibility

The following code has been recently updated to ensure compatibility with AX4, avoiding reliance on functions introduced in later versions (AX09 or AX12), such as TableId2Name. Instead, it leverages an encapsulated dictTable instance to achieve the same result, retrieving the name property as needed.

This approach guarantees seamless functionality across different AX versions, making it a reliable choice for your Editor Scripts Class.

 

// Begin : Felipe Nogueira, DevTools
public void template_FindByPrimaryIndex(Editor editor)
{
//Macro
#Properties
#define.dialogCaption("DevTools - Find Method generator")
#define.dialogText("Do you wish to generate find based on %1")
#define.findMthName("find")
#define.findRecIdMthName("findRecId")
//Variables
TreeNode aotNode = EditorScripts::getVersionControllableNode(editor);
xppSource xppSource = new xppSource();
TableName tableNameUpper, buffer;
Source whereFunction, xmlParms, methodName;
DialogButton noYesCancelDialogButton;
//Inner Methods
str getFieldEdt(fieldName _fieldName)
{
TreeNode treeNode = aotNode.AOTfirstChild();
dictField curField;
int edtId;
str edtName;
DictType dictType;
;
treeNode = treeNode.AOTfindChild(_fieldName);
curField = new dictField(TableName2Id(TableNameUpper), fieldName2Id(TableName2Id(TableNameUpper),treeNode.AOTname()));
edtId = curField.typeId();
if (edtId)
{
dictType = new DictType(edtId);
edtName = dictType.name();
}
else if (curField.type() == Types::String)
{
edtName = 'str';
}
else if ( curField.type() == Types::Int64)
{
edtName = 'Int64';
}
return edtName;
}
TreeNode runDialog()
{
TreeNode treeNodeIndexes = aotNode.AOTfindChild(literalStr(Indexes));
TreeNode treeNodeCurIdx = treeNodeIndexes.AOTfirstChild();
container conUniqueIdx;
int i;
;
while (treeNodeCurIdx)
{
if (treeNodeCurIdx.AOTgetProperty(literalStr(allowDuplicates)) == literalStr(No))
{
conUniqueIdx += treeNodeCurIdx.AOTname();
}
treeNodeCurIdx = treeNodeCurIdx.AOTnextSibling();
}
conUniqueIdx += #findRecIdMthName;
for (i = 1; i <= conLen(conUniqueIdx); i++)
{
noYesCancelDialogButton = Box::yesNoCancel(strFmt("%1 %2",#dialogText, conPeek(conUniqueIdx,i)), DialogButton::Yes, #dialogCaption);
if (noYesCancelDialogButton == DialogButton::Yes)
{
if (conlen(conUniqueIdx) == i)
{
treeNodeCurIdx = null;
methodName = #findRecIdMthName;
}
else
{
treeNodeCurIdx = treeNodeIndexes.AOTfindChild(conPeek(conUniqueIdx,i));
methodName = #findMthName;
if (treeNodeCurIdx)
{
treeNodeCurIdx = treeNodeCurIdx.AOTfirstChild();
}
}
break;
}
else if (noYesCancelDialogButton == DialogButton::Cancel || i == conLen(conUniqueIdx) )
{
throw error("@SYS107513");
}
}
return treeNodeCurIdx;
}
void addMethodParm(fieldName _parameter, fieldName _fieldName)
{
str aux = @'%1/// <param name="%2">
/// The %3 of the <c>%4</c> record to find.
/// </param>';
;
xmlParms += strFmt(aux, num2char(10), _parameter, _fieldName, tableNameUpper);
}
Source initializeParametersByPrimaryIndex()
{
Source ret,auxStr;
fieldName fieldName, parameter;
TreeNode treeNode = runDialog();
;
if (!treeNode)
{
fieldName = literalStr(RecId);
parameter = "_" + strLwr(subStr(fieldName,1,1)) + subStr(fieldName,2,strLen(fieldName)-1);
addMethodParm(parameter,fieldName);
ret = ret ? ret + strfmt("%1 %2", strFmt(",%1 %2",num2char(10) ,fieldName), parameter)
: strfmt("%1 %2", fieldName, parameter);
whereFunction = whereFunction ? whereFunction + strFmt("%1 &&",num2char(10))
: literalStr(where);
whereFunction += strFmt(" %1.%2 == %3",buffer,fieldName, parameter);
}
while (treeNode)
{
fieldName = treeNode.AOTgetProperty(literalStr(DataField));
parameter = "_" + strLwr(subStr(fieldName,1,1)) + subStr(fieldName,2,strLen(fieldName)-1);
addMethodParm(parameter,fieldName);
ret = ret
? ret + strfmt("%1 %2", strFmt(",%1 %2",num2char(10) ,getFieldEdt(fieldName)), parameter)
: strfmt("%1 %2", getFieldEdt(fieldName), parameter);
whereFunction = whereFunction ? whereFunction + strFmt("%1 &&",num2char(10))
: literalStr(where);
whereFunction += strFmt(" %1.%2 == %3",buffer,fieldName, parameter);
treeNode = treeNode.AOTnextSibling();
}
return ret;
}
Source template()
{
;
return @'/// <summary>
/// Finds the specified record in the <c>%1</c> table.
/// </summary>%6
/// <param name="_forupdate">
/// A Boolean value that indicates whether to read the record for update; optional.
/// </param>
/// <param name="_concurrencyModel">
/// The table selection ConcurrencyModel; optional.
/// </param>
/// <returns>
/// A record in the <c>%1</c> table; otherwise, an empty record.
/// </returns>
public static %1 %5(%3, boolean _forupdate = false,
ConcurrencyModel _concurrencyModel = ConcurrencyModel::Auto)
{
%1 %2;
;
%2.selectForUpdate(_forupdate);
if (_forupdate && _concurrencyModel != ConcurrencyModel::Auto)
{
%2.concurrencyModel(_concurrencyModel);
}
select firstonly %2
%4;
return %2;
}';
}
;
//SourceCode Start
if (strfmt("%1",aotNode.applObjectType()) == #PropertyTable)
{
tableNameUpper = aotNode.AOTname();
buffer = strLwr(subStr(tableNameUpper,1,1)) + subStr(tableNameUpper,2,strLen(tableNameUpper)-1);
editor.insertLines(strFmt(template(),tableNameUpper,buffer,initializeParametersByPrimaryIndex(),whereFunction, methodName, xmlParms));
editor.gotoLine(1);
}
}
// Place holders template Map
/*
%1 - TableName UpperCase
%2 - buffer
%3 - innerMth: parametersList (str separeted by ',')
%4 - whereFunction (Where statement with selected fields)
%5 - find Method Name
%6 - Parms on XML Summary
*/
// End: Felipe Nogueira, DevTools
On Tue, 2 Jul 2024 at 18:03, Felipe Nogueira <felipenog1@gmail.com> wrote:
// Begin : Felipe Nogueira, DevTools
public void FN_FindByPrimaryIndex(Editor editor)
{
//Macro
#Properties
#define.dialogCaption("DevTools - Find Method generator")
#define.dialogText("Do you wish to generate find based on %1")
#define.findMthName("find")
#define.findRecIdMthName("findRecId")
//Variables
TreeNode aotNode = EditorScripts::getVersionControllableNode(editor);
xppSource xppSource = new xppSource();
TableName tableNameUpper, buffer;
Source whereFunction, xmlParms, methodName;
DialogButton noYesCancelDialogButton;
//Inner Methods
str getFieldEdt(fieldName _fieldName)
{
TreeNode treeNode = aotNode.AOTfirstChild();
dictField curField;
int edtId;
str edtName;
DictType dictType;
;
treeNode = treeNode.AOTfindChild(_fieldName);
curField = new dictField(TableName2Id(TableNameUpper), fieldName2Id(TableName2Id(TableNameUpper),treeNode.AOTname()));
edtId = curField.typeId();
if (edtId)
{
dictType = new DictType(edtId);
edtName = dictType.name();
}
return edtName;
}
TreeNode runDialog()
{
TreeNode treeNodeIndexes = aotNode.AOTfindChild(literalStr(Indexes));
TreeNode treeNodeCurIdx = treeNodeIndexes.AOTfirstChild();
container conUniqueIdx;
int i;
;
while (treeNodeCurIdx)
{
if (treeNodeCurIdx.AOTgetProperty(literalStr(allowDuplicates)) == literalStr(No))
{
conUniqueIdx += treeNodeCurIdx.AOTname();
}
treeNodeCurIdx = treeNodeCurIdx.AOTnextSibling();
}
conUniqueIdx += #findRecIdMthName;
for (i = 1; i <= conLen(conUniqueIdx); i++)
{
noYesCancelDialogButton = Box::yesNoCancel(strFmt("%1 %2",#dialogText, conPeek(conUniqueIdx,i)), DialogButton::Yes, #dialogCaption);
if (noYesCancelDialogButton == DialogButton::Yes)
{
if (conlen(conUniqueIdx) == i)
{
treeNodeCurIdx = null;
methodName = #findRecIdMthName;
}
else
{
treeNodeCurIdx = treeNodeIndexes.AOTfindChild(conPeek(conUniqueIdx,i));
methodName = #findMthName;
if (treeNodeCurIdx)
{
treeNodeCurIdx = treeNodeCurIdx.AOTfirstChild();
}
}
break;
}
else if (noYesCancelDialogButton == DialogButton::Cancel || i == conLen(conUniqueIdx) )
{
throw error("@SYS107513");
}
}
return treeNodeCurIdx;
}
void addMethodParm(fieldName _parameter, fieldName _fieldName)
{
str aux = @'%1/// <param name="%2">
/// The %3 of the <c>%4</c> record to find.
/// </param>';
;
xmlParms += strFmt(aux, num2char(10), _parameter, _fieldName, tableNameUpper);
}
Source initializeParametersByPrimaryIndex()
{
Source ret,auxStr;
fieldName fieldName, parameter;
TreeNode treeNode = runDialog();
;
if (!treeNode)
{
fieldName = literalStr(RecId);
parameter = "_" + strLwr(subStr(fieldName,1,1)) + subStr(fieldName,2,strLen(fieldName)-1);
addMethodParm(parameter,fieldName);
ret = ret ? ret + strfmt("%1 %2", strFmt(",%1 %2",num2char(10) ,fieldName), parameter)
: strfmt("%1 %2", fieldName, parameter);
whereFunction = whereFunction ? whereFunction + strFmt("%1 &&",num2char(10))
: literalStr(where);
whereFunction += strFmt(" %1.%2 == %3",buffer,fieldName, parameter);
}
while (treeNode)
{
fieldName = treeNode.AOTgetProperty(literalStr(DataField));
parameter = "_" + strLwr(subStr(fieldName,1,1)) + subStr(fieldName,2,strLen(fieldName)-1);
addMethodParm(parameter,fieldName);
ret = ret
? ret + strfmt("%1 %2", strFmt(",%1 %2",num2char(10) ,getFieldEdt(fieldName)), parameter)
: strfmt("%1 %2", getFieldEdt(fieldName), parameter);
whereFunction = whereFunction ? whereFunction + strFmt("%1 &&",num2char(10))
: literalStr(where);
whereFunction += strFmt(" %1.%2 == %3",buffer,fieldName, parameter);
treeNode = treeNode.AOTnextSibling();
}
return ret;
}
Source template()
{
;
return @'/// <summary>
/// Finds the specified record in the <c>%1</c> table.
/// </summary>%6
/// <param name="_forupdate">
/// A Boolean value that indicates whether to read the record for update; optional.
/// </param>
/// <param name="_concurrencyModel">
/// The table selection ConcurrencyModel; optional.
/// </param>
/// <returns>
/// A record in the <c>%1</c> table; otherwise, an empty record.
/// </returns>
public static %1 %5(%3, boolean _forupdate = false,
ConcurrencyModel _concurrencyModel = ConcurrencyModel::Auto)
{
%1 %2;
;
%2.selectForUpdate(_forupdate);
if (_forupdate && _concurrencyModel != ConcurrencyModel::Auto)
{
%2.concurrencyModel(_concurrencyModel);
}
select firstonly %2
%4;
return %2;
}';
}
;
//SourceCode Start
if (strfmt("%1",aotNode.applObjectType()) == #PropertyTable)
{
tableNameUpper = aotNode.AOTname();
buffer = strLwr(subStr(tableNameUpper,1,1)) + subStr(tableNameUpper,2,strLen(tableNameUpper)-1);
editor.insertLines(strFmt(template(),tableNameUpper,buffer,initializeParametersByPrimaryIndex(),whereFunction, methodName, xmlParms));
editor.gotoLine(1);
}
}
// Place holders template Map
/*
%1 - TableName UpperCase
%2 - buffer
%3 - innerMth: parametersList (str separeted by ',')
%4 - whereFunction (Where statement with selected fields)
%5 - find Method Name
%6 - Parms on XML Summary
*/
// End: Felipe Nogueira, DevTools

 

Hope it might be helpful,
Thanks,
Felipe Nogueira