Extending defaulting logic in tables
Views (257)
Hello AX World,
In the previous post, I've explained about defaulting logic in tables. In this post, I will explain how to properly extend it. The emphasis is on semantics of dependencies method rather than implementation of the defaulting logic.
When extending you should always assume dependencies exist, even if they don't at this moment. It's very simple to introduce a breaking change when extending defaulting logic, therefore we will try not to. Well... except the case 3 and 4, which is a breaking change by nature.
When extending you should always assume dependencies exist, even if they don't at this moment. It's very simple to introduce a breaking change when extending defaulting logic, therefore we will try not to. Well... except the case 3 and 4, which is a breaking change by nature.
I have identified the following different cases of extensions on defaulting logic:
- Add a dependency on a custom field.
- Add a dependency on a standard field.
- Overwrite dependency.
- Remove dependency.
Let's explore them one by one.
This is the data model we have for this example. It comes from the previous post and it's very basic.
(1) Add a dependency on a custom field
I have added a new field TotalCur that needs some defaulting logic.
This is the simplest case. You only need to add new dependencies to the existing container.
What's important is to properly add to the container. Have a look at getDefaultingDependencies method.
You need to form a dependency container of the same structure, then, append the original container.
Method getExtension should be extended and tested if defaulting logic handler class is instantiated.
Of course, you can ignore the standard or, if you know standard exists, use it and assume that it will never go away. However, I recommend to test.
Method defaultField should be used to implement defaulting logic for a new field. It's identical to validateField or modifiedField, therefore I will not put too much effort on it.
Here is how the code tools like:
[ExtensionOf(tableStr(TestDefaultingFields))]
final class MyTestDefaultingFields_Extension
{
public container getDefaultingDependencies()
{
container ret = next getDefaultingDependencies();
ret += [
[fieldNum(TestDefaultingFields, TotalCur),
[fieldNum(TestDefaultingFields, Price),
fieldNum(TestDefaultingFields, Qty)]]
];
return ret;
}
public void defaultField(FieldId _fieldId)
{
next defaultField(_fieldId);
switch (_fieldId)
{
case fieldNum(TestDefaultingFields, TotalCur):
//Defaulting logic implementation
break;
}
}
public TableExtension getExtension()
{
TableExtension ret = next getExtension();
if (!ret)
{
ret = SysTableExtension::construct();
}
return ret;
}
}
(2) Add a dependency on a standard field
Let's add a new field Discount that will require a new dependency to be added to the field Total and modify it's defaulting logic (total calculation). For simplicity, I will ignore TotalCur that was added in the previous case.This case requires checking if the field has dependencies and if it does, append it.
I have created a helper method to merge dependency containers. This method is quite a generic one. It will add any dependencies that does not exist in the original dependency container.
You can create this method in the Global class extension or where it makes sense for you.
We use the same structure for dependencies and later we call this helper method to merge dependencies.
Here is the code example:
public container getDefaultingDependencies()
{
container ret = next getDefaultingDependencies();
container newCon = [
[fieldNum(TestDefaultingFields, Total),
[fieldNum(TestDefaultingFields, Discount)]]
];
ret = mergeDependencies(ret, newCon);
return ret;
}
public void defaultField(FieldId _fieldId)
{
next defaultField(_fieldId);
switch (_fieldId)
{
case fieldNum(TestDefaultingFields, Total):
this.Total = this.Qty * this.Price - this.Discount;
break;
}
}
...and here is the code for merging the containers. Quite a chunk.
public static container mergeDependencies(container _orig, container _new, boolean _overwrite = false)
{
for (int c = 1; c <= conLen(_orig); c++)
{
FieldId origFieldId;
container origDependencies;
container origElement = conPeek(_orig, c);
if (conLen(origElement) == 2)
{
[origFieldId, origDependencies] = origElement;
}
else if (conLen(origElement) == 1)
{
[origFieldId] = origElement;
}
for (int c2 = 1; c2 <= conLen(_new); c2++)
{
FieldId newFieldId;
container newDependencies;
container newElement = conPeek(_new, c2);
if (conLen(newElement) == 2)
{
[newFieldId, newDependencies] = newElement;
}
else if (conLen(newElement) == 1)
{
[newFieldId] = newElement;
}
if (origFieldId == newFieldId)
{
if (_overwrite)
{
origDependencies = newDependencies;
}
else //Append
{
for (int d2 = 1; d2 <= conLen(newDependencies); d2++)
{
FieldId newDependencyId = conPeek(newDependencies, d2);
if (!conFind(origDependencies, newDependencyId))
{
origDependencies += newDependencyId;
}
}
}
_orig = conPoke(_orig, c, [origFieldId, origDependencies]);
_new = conDel(_new, c2, 1); //Once the dependency was appended, remove from the list
c2--;
}
}
}
_orig += _new; //Append the rest, which are not in the list
return _orig;
}
(3) Overwrite dependency
This case is to replace dependencies for a field. Let's say you would like some different behavior on a standard fields. Note that it's a breaking change. Therefore, instead of appending the list we are going to overwrite it for the fields specified in your dependency container. If the field were not in the list, standard dependencies would be applied for it.Of course, it could be even more complicated, to overwrite a specific set of fields but we won't go that road.
For this, we can use the same method, as if you noticed the third optional parameter is to overwrite dependencies.
Here is the dummy code sample to replace dependencies for the field Total. After this, it will only depend on Discount field and will ignore Price and Qty field modifications.
public container getDefaultingDependencies()
{
container ret = next getDefaultingDependencies();
container newCon = [
[fieldNum(TestDefaultingFields, Total),
[fieldNum(TestDefaultingFields, Discount)]]
];
ret = mergeDependencies(ret, newCon, true);
return ret;
}
(4) Remove dependencies
For this case, I have created yet another helper method removeDependencies. It takes dependencies and a field Id as a parameter. It removes dependencies for that particular field only. This is a breaking change too.
This is just an idea. Of course, it can be implemented in various ways.
Here is the code example for that:
public container getDefaultingDependencies()
{
container ret = next getDefaultingDependencies();
ret = removeDependencies(ret, fieldNum(TestDefaultingFields, Total));
return ret;
}
...and the remove dependencies method.
public static container removeDependencies(container _orig, FieldId _fieldId)
{
for (int c = 1; c <= conLen(_orig); c++)
{
container origElement = conPeek(_orig, c);
if (conLen(origElement) > 0)
{
FieldId origFieldId = conPeek(origElement, 1);
if (origFieldId == _fieldId)
{
return conDel(_orig, c, 1);
}
}
}
return _orig;
}
We have reviewed four different cases of extending table's defaulting logic. Now I would like to share a nice example I found in standard functionality, which has been developed with extensions in mind.
Extensible defaulting logic code
Have a look at the table InventItemBarcode. Every field's defaulting logic has a separate method in the method defaultField.
I wish all the code looked like that. :)
OK. That's all I wanted to share in this blog post.
It you have something to add, feel free to comment.
Be aware and take care!
This was originally posted here.
*This post is locked for comments