A collection of X++ gists for those occasions when the help pages don’t cut to the chase!
Extensions
It is important to following the naming guidelines when extending an existing object to ensure uniqueness across the Application Object Tree over time.
Table extensions
Additional methods are added to an existing table by creating a new class with an attribute declaring that it extends the existing table:
/// <summary> | |
/// Form extensions should have an Extension suffix, and should include the name | |
/// of the extending model as an infix to maintain uniqueness. The extension class | |
/// must be marked as final as it would make no logical sense to subclass | |
/// or extend an extension. | |
/// </summary> | |
[ExtensionOf(tableStr(HcmWorker))] | |
public final class HcmWorker_MyNewModel_Extension | |
{ | |
public display Name someOtherName() | |
{ | |
return 'someOtherName'; | |
} | |
} |
Class extensions
Additional behaviours are added to existing class methods by creating a new class with an attribute declaring that it extends the existing class:
/// <summary> | |
/// Class extensions should have an Extension suffix, and should include the name | |
/// of the extending model as an infix to maintain uniqueness. The extension class | |
/// must be marked as final as it would make no logical sense to subclass | |
/// or extend an extension. | |
/// </summary> | |
[ExtensionOf(classStr(CustTransSettlement))] | |
final class CustTransSettlement_MyNewModel_Extension | |
{ | |
protected boolean isTransTypeValidForInsertSettlementLines(CustTrans _custTrans) | |
{ | |
//We must always call the next handler in the Chain of Command (CoC) even if | |
//we really don't wan't or need to. | |
//The only guarantee about sequence is that the standard version will run last. | |
next isTransTypeValidForInsertSettlementLines(_custTrans); | |
//Override the standard result with our own logic, but put the logic somewhere else | |
//to keep this method assimple as possible to protect against future | |
//framework changes. | |
return MyOtherClass::SomeOtherMethod(_custTrans); | |
} | |
} |
Delegates
Use multicast delegates to create extension points for other developers to subscribe to.
void myMethod() | |
{ | |
EventHandlerResult result = new EventHandlerResult(); | |
this.nydelegate(1, 2, result); | |
var total = result.result(); | |
} | |
delegate void myDelegate(real parm1, real parm2, EventHandlerResult result) | |
{ | |
//Must be protected, void and empty! | |
//Add an EventHandlerResult to allow the subscriber to return a value | |
} |
Remember that the order in which the handlers receive the invocation isn’t predictable.
Event Handlers
Take care not to add event handlers to a class that is already extending a table as the compiler will incorrectly add these methods to the Common object.
Delegate events
Subscribing to a multicast delegate:
Form events
Handling a form event:
Form data-source events
Handling a form data-source event:
Form control events
Handling a form control event:
Table events
Handling a table event:
[DataEventHandler(tableStr(FMVehicle), DataEventType::ValidatedWrite)] | |
public static void FMVehicle_onValidatedWrite(Common sender, DataEventArgs e) | |
{ | |
ValidatedEventArgs validateArgs = e as ValidatedEventArgs; | |
FMVehicle vehicle = sender as FMVehicle; | |
boolean result = validateArgs.parmValidateResult(); | |
if (vehicle.NumberOfCylinders == 0) | |
{ | |
result = checkFailed('Invalid number of cylinders.'); | |
validateArgs.parmValidateResult(result); | |
} | |
} |
Enumerations
Iterating enums
Regardless of the language, you should only ever reference an enum by its elements (and not it’s intrinsic value). Historically, X++ developers have sometimes referred to the enumeration’s intrinsic values. This is no longer supported in D365FO as these values are determined at runtime.
DictEnum dictEnum = new DictEnum(enumNum(ProjFundingType)); | |
if (dictEnum) | |
{ | |
int enumValueCount = dictEnum.values(); | |
//Iterate on positions, not values!! | |
for (int enumValueIndex = 0; enumValueIndex < enumValueCount; enumValueIndex++) | |
{ | |
if (dictEnum.index2Value(enumValueIndex) != ProjFundingType::Customer) | |
{ | |
doSomething(dictEnum.index2Value(enumValueIndex); | |
} | |
} | |
} |
Using enums in a switch
We can also use the elements from an enum in a switch statement:
SysDictEnum dictEnum = new SysDictEnum(enumNum(ProjSalesPriceMarkup)); | |
int enumValueCount = dictEnum.values(); | |
for (int enumValueIndex = 0; enumValueIndex < enumValueCount; enumValueIndex++) | |
{ | |
ProjSalesPriceMarkup value = dictEnum.index2Value(enumValueIndex); | |
str actual = dictEnum.index2Label(enumValueIndex); | |
str expected = strMin(); | |
switch (value) | |
{ | |
case ProjSalesPriceMarkup::No : | |
expected = "@SYS26062"; | |
break; | |
case ProjSalesPriceMarkup::Yes : | |
expected = "@SYS80120"; | |
break; | |
case ProjSalesPriceMarkup::MarkupPct : | |
expected = "@SYS78909"; | |
break; | |
default : | |
break; | |
} | |
} |
Working with XML
Iterating a list of XML nodes
public boolean elementExists(str elementName, str attributeName, str attributeValue) | |
{ | |
XmlNodeList nodes = xmlDocument.GetElementsByTagName(elementName); | |
XMLNodeListIterator iterator = new XMLNodeListIterator(nodes); | |
while (iterator.moreValues()) | |
{ | |
XmlElement e = iterator.value(); | |
str val = e.getAttribute(attributeName); | |
if (val == attributeValue) | |
{ | |
return true; | |
} | |
iterator.nextValue(); | |
} | |
return false; | |
} |
More gists to follow…