I am investigating the capabilities of the new delegate & event subscription pattern in AX 2012.
At the moment I am looking to detect when a particular field has been modified, for example when SalesTable.SalesStatus is changed to SalesStatus::Invoiced.
I have created the following post-event handler and attatched to the SalesTable.Update method;
public static void SalesTable_UpdatePosteventHandler(XppPrePostArgs _args)
{
Info("Sales Update Event Handler");
}
Now I know I can get the SalesTable from the _args, but how can I detect a field has changed? I could really use a before & after version, which makes me think I am subscribing to the wrong event here.
If the update method does not update the field, you can use a pre event handler on the update method. If you want to monitor the PriceGroup field on the CustTable table then create a class called CustTableEventHandler containing this method:
public static void preUpdateHandler(XppPrePostArgs _args)
{
CustTable custTable = _args.getThis();
if (custTable.PriceGroup != custTable.orig().PriceGroup)
info(strFmt("Change price group from '%1' to '%2'", custTable.orig().PriceGroup, custTable.PriceGroup));
}
A post event handler will not work, as orig() will return the changed record.
Also if the the record is updated using doUpdate your handler is not called.
You could also override the aosValidateUpdate on CustTable, which is called even if doUpdate is used. This method is always run on the AOS server.
public boolean aosValidateUpdate()
{
boolean ret = super();
if (this.PriceGroup != this.orig().PriceGroup)
info(strFmt("Change price group from '%1' to '%2'", this.orig().PriceGroup, this.PriceGroup));
return ret;
}
Yet another option would be a global change to the Application.eventUpdate method.
From the header of the method:
Serves as a callback that is called by the kernel when a record in a
table is updated, provided that the kernel has been set up to monitor
records in that table.
A developer can set up the kernel to call back on updates for a given
table by inserting a record into the DatabaseLog kernel table with all
fields set to relevant values, which includes the field logType set to
EventUpdate. It is possible to set up that the kernel should call back
whenever a record is updated or when a specific field is updated.This
is very similar to how logUpdate is called and set up. The call
of this method will be in the transaction in which the record is
updated.
This method is used by the alert rule notification system. I would recommend against this, unless it is a global change (like alert rules).
Alert rules can be extended as described here.
Related
I want to initialize a value of an edit method inside the init method of form, i wrote this:
[Form]
public class foo extends FormRun
{
str paymTermId;
public void init()
{
CustTable custTable = CustTable::find("DE-001");
paymTermId = custTable.paymTermId;
super();
}
edit str edtpaymTermId(boolean set, str _paymTermId)
{
if (set)
{
paymTermId= _paymTermId;
}
return paymTermId ;
}
}
But when i open the form the control remains empty.
any suggestions?
I tried to reproduce the issue, but was not successful. For me, when opening the form, the control shows a value.
A possible reason why it is not working for you could be that you open the form in the wrong company. In your code, you retrieve the value to display in the control from the payment term of customer DE-001. This customer exists in company USMF in the Contoso demo data and has payment term Net10. If the form is opened in this company, the value is shown in the control. If you are in another company (e.g. DAT), no value is shown.
I see 2 things that are wrong:
You are setting the value BEFORE super(). It should be after.
You SHOULDN'T initialize the value via field, you should do it calling edit method. Edit methods have a boolean SET parameter which can simulate a call for setting a value.
I have created a command button in the CustGroup form action pane.
I have added a new base enum edt field to both the CustGroup and CustTable tables and forms.
When you click on the button the data that was previously changed in the CustGroup table must be reflected in the cust table form.
I have written code in button on click event handler but it's not updating.
What to do, any suggestions?
If I understand your question correctly, you want to transfer a change of a new field in a customer group to all customers that share this customer group.
This kind of mass data update is usually not done by code in a form, because that code is executed on the client tier, which results in a bad performance. Instead, you should create a class that is set to execute on the server tier. If you create a main method for this class, you can easily create an action menu item for it, which let's you easily integrate the call to this class as a button in the CustGroup form.
In the main method you can access the CustGroup record for which the button was clicked via the Args object. This gives you the value of your new field that was changed. With this value, you can then use code similar to the following to update your customers:
public void updateCustomersWithNewCustGroupFieldValue(CustGroup _custGroup)
{
CustTable custTable;
ttsBegin;
while select forUpdate custTable
where custTable.CustGroup == _custGroup.CustGroup
{
custTable.MyNewEnumField = _custGroup.MyNewEnumField;
if (custTable.validateWrite())
{
custTable.update();
}
else
{
error('Please implement some error handling');
}
}
ttsCommit;
}
I've created a simple form with an enum field on a grid, dragged from the DataSource CompanyImage:
Table CompanyImage has an Index on this field named Brand in my example and AllowDuplicates is set to No :
And here is the form:
I've overridden the close() method of the form like this:
public void close()
{
CompanyImage_ds.write();
super();
}
An error is displayed when I close it saying that
"Cannot create a record in CompanyImage(CompanyImage). Legal entities: Example1.
The record already exists."
That's fine but I would like a way to stop closing the window when this happens. A validateWrite() would be nice but I am not really able to figure out where and what to write in order to accomplish this behavior.
I mean, how to check that new row is added and it contains a field that already exists in the table ?
You shouldn't have to force the write() method. Closing the form should already do it.
If you wish to check something to allow the form to be closed, the close() method is too late in execution. You should leverage the canClose() method.
You could override the validate method of the grid column. You would need to write some validation logic in that method but that would prevent the column from saving at all if validation failed.
public boolean validate()
{
boolean ret;
// write your own validation logic
if (validation logic is true)
{
ret = true;
}
return ret;
}
I have a query-based report in which the query has some interactive ranges enabled on them. This is great except the value is blank, or has the last values used pre-populated. One of these is Vendor account number. If I wanted to have this report to pre-populate the Vend account based on whichever Vendor account record is selected (the caller), how would I be able to achieve this?
The answer was easy, although hard to find. I wasn't aware that you could access query objects from within a controller. The solution is to create a Controller class with only a main() method defined as normal, and the prePromptModifyContract method overridden. The following code will solve the problem:
SomeTable someTable;
Query query;
super();
if (this.parmArgs() && this.parmArgs().dataset() == tableNum(SomeTable))
{
someTable = this.parmArgs().record();
query = this.getFirstQuery();
SysQuery::findOrCreateRange(query.dataSourceTable(tableNum(SomeOtherTable)), fieldNum(SomeOtherTable, SomeOtherField)).value(SysQuery::value(someTable.SomeField));
}
I haven't tried this, but you could override the query's init method and call to element.args().record() as in a Form.
Something like this:
public void init()
{
VendTable vendTable;
super();
if (element.args().dataset() == tableNum(VendTable))
{
vendTable = element.args().record();
//populate your ranges with vendTable
}
}
I hope it works!
I have created a wizard in Ax 2012 using wizard>wizard and i am calling this wizard from Custtablelistpage form... now, i have put some controls in this wizard like CustAccount, and i need to initialize value in this control from selected record in Custtablelistpage form....
I am trying to perform this using Args class, but it is not working, please suggest some solutions..
please create one wizard in AX 2012 using tools>wizard>wizard
then, please put menu item of this wizard somewhere on custtablelistpage.
After that, please put one field named Customer account on welcome tab of wizard.
Now, if you any record that is displayed in custtablelistpage form, please select that.
My task is to display the Account num of selected record to my wizard when i am clicking the menu item button which i have put on custtablelistpage.
Actually, i have written some code,, which is is working absolutely fine for normal forms. but it is not working for Wizard and i am not getting value to initialize in my control on wizard.
Ok, I took some time to try this out and I have two possible solutions for you.
You can do it by using unbound controls and pass in the selected record
Or you could use a datasource on the wizard form and filter on the selected values
First let's try and do it by using a simple unbound control. Start by adding a CustTable member variable and parameter method to your wizard class.
public class MyTestWizardWizard extends SysWizard
{
CustTable mySelectedCustomer;
}
public CustTable parmMySelectedCustomer(CustTable _mySelectedCustomer = mySelectedCustomer)
{
;
mySelectedCustomer = _mySelectedCustomer;
return mySelectedCustomer;
}
Then in your form, you can overwrite the init method and do the following :
void init()
{
int controlid;
FormStringControl fsControl;
;
super();
if (element.Args().caller())
{
sysWizard = element.Args().caller();
// Get the control id of the CustomerId control
controlid = element.controlId(formControlStr(MyTestWizardWizard, CustomerId));
// Check if we actually have a form string control
if(element.control(controlid) is FormStringControl)
{
// Cast to the FormStringControl type
fsControl = element.control(controlid) as FormStringControl;
// Now fill in the field value
fsControl.text(sysWizard.parmMySelectedCustomer().AccountNum);
}
}
else
{
MyTestWizardWizard::main(new args());
element.closeCancel();
}
}
So what you actually do here is just fetch the selected record stored in you wizard class. Then we check if the control we want to assign values to is actually the right control to put the value in.
Though this is working, I would prefer a second method. That would be to use a datasource on the form and put a range on the selected record like this. Just put the CustTable as a datasource on the form and place your control as you would normally do.
Then, make sure the init method is performing the super() call at the bottom to make sure initialisation is done before calling the datasource methods:
void init()
{
;
// make sure the sysWizard is already initialized before the super to make sure the init on the datasource has an instance of sysWizard
if (element.Args().caller())
{
sysWizard = element.Args().caller();
}
else
{
MyTestWizardWizard::main(new args());
element.closeCancel();
}
super();
}
Then overwrite the init method on the datasource to put a range on the recId field of the custTable.
Please mind the you could assign the value of the range in the ExecuteQuery method, but for this case, I just do it here.
public void init()
{
;
super();
SysQuery::findOrCreateRange(this.query().dataSourceTable(tableNum(CustTable)), fieldNum(CustTable, RecId)).value(queryValue(SysWizard.parmMySelectedCustomer().RecId));
}
Now when your wizard is run, the args passes the record to your wizard class, the form picks it up on the init of the datasource and puts a range on the record that you have selected. All the rest of the magic is normal Ax behavior with bound data controls.
So I hope this is what you needed. Please let me know if you have further questions.