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;
}
Related
I am struggling with a custom validation in an EditForm using basic validation in my Blazor server-side app. In this MS doc it is stated
A handler for the OnValidationRequested event of the EditContext
executes custom validation logic. The handler's result updates the
ValidationMessageStore instance.
but nothing is mentioned on how to inform the validation logic if the custom validation was successful or failed. The code below shows how error messages can be added in a custom validation handler, but OnValidSubmit is called during the await-ing of the DB query in the custom validation handler. So it seems necessary, to perform the custom validation also in OnValidSubmit, which is acceptable in my case but not nice.
Q: Is there no other/nicer way to inform the validation logic about the result of a custom validation in order to prevent having to re-check inside OnValidSubmit?
Here is my code in OnParametersSetAsync:
// Create EditContext
editContext = new EditContext(assignment);
// Create additional message store for the custom validation messages
validationMessageStore = new(editContext);
// Add additional validation handler
editContext.OnValidationRequested += OnValidationRequestedAsync;
Here is the code for the custom validation handler:
private async void OnValidationRequestedAsync(object sender, ValidationRequestedEventArgs e)
{
// clear previous error messages
validationMessageStore.Clear();
// check DB if Title already exists
bool exists = await myService.IsPresent(myModel.Title);
// While waiting for this async process, OnValidSubmit gets called
if (exists)
{
// yes, so add a validation message
validationMessageStore.Add(() => myModel.Title, "The Title is already used.");
// inform ValidationSummary that a new error message has been added
editContext.NotifyValidationStateChanged();
}
}
In OnValidSubmit I have to re-check:
// check DB if Title already exists
bool exists = await myService.IsPresent(myModel.Title);
if (exists)
{
// do nothing if invalid (error message is displayed by OnValidationRequestedAsync)
return;
}
// ...
Have you looked at Blazored.Validation? It takes all the pain out of this. You create a validator (or you can use attributes, but for custom validation, I find the validator approach much easier), then simply add the <FluentValidationValidator /> tag inside your form, and it all just works. Validation is fired as appropriate, messages are shown as expected and you OnValidSubmit just doesn't get called if there are validation errors.
Hope that helps.
I found a possible solution (at least in general) by decorating the property with a custom validation attribute. Just add
[CustomValidation(typeof(myModelClass), "OnValidateTitle")]
public string Title{ get; set; }
to the property of the model class and add the static method in the model class that performs the custom validation and returns an appropriate status
public static ValidationResult OnValidateTitle(string title)
{
if (...)
{
return new ValidationResult("Title is ...");
}
else
{
return ValidationResult.Success;
}
}
While this approach should work in general, in my particular case it didn't work, because I do not have access to the database context from the static method.
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.
On Dynamics AX 2012,Passing between two step form a parameter that I would use to change the data source of the second form;
how you pass a parameter from the init form to init the data source?
I hope I have understood the question
If you wanto to pass a parameter between forms, you have multiway.
One solution.
In Form - A override a method clicked() control Button
void clicked()
{
Args args;
FormRun formRun;
;
args = new Args();
args.name(formstr(nameyourFormB));
args.record(nameTableSourceRecords);
args.caller(element);
formRun=new FormRun(args);
formRun.run();
formRun.wait();
}
So , in the SecondForm - Form - B
override method init()
public void init()
{
super();
if(element.args() && element.args().record() &&element.args().record().TableId == tableNum(nameSourceRecords))
{
nameTableSourceRecords = element.args().record() ;
stringEdit.text(nameTableSourceRecords.nameFieldTableSourceRecords);
}
}
You have to insert in Designs node Form-B a one StringEdite (set AutoDeclaration YES) in Properties.
Now, you open Form-A select a record, click on control Button -> will Open Form-B and you have set a value in your StringEdit control.
I hope to help you.
Greetings!
Get the parameter in the form.init(), save it to a variable in your form's classdeclaration, then override the datasource's init() method and manually create a FormDataSource object using the passed in parameter to determine the datasource.
Although I'm not sure how you're going to show this on form controls...the controls will expect the datasource to be what it is set up as. There's probably a better way to achieve whatever you're trying to do.
One solution of this is to use EnumTypeParameter and EnumParameter property on menuitem of child form. Set these parameter value on parent form and on child form init you just need an if clause then. like:
if (args.parmEnumType() == yourEnum && args.parmEnum() == 'yourEnumValue')
{
//set the desired datasource
}
these links may help you:
opening a form on basis of menuitem
and
xArgs.parmEnumType
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.
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.