On a form data source (SalesLine) I have a validateWrite method, which in turn calls the super() method to call the validateWrite method on the SalesLine table, amongst other checks.
In the SaleLine table I have custom functionality for recording, and sometime stopping, data changes.
I don't want this functionality to be triggered when I write to SalesLine from my new form. Therefore I want to check a condition, within the validateWrite method on the SalesLine table, to find out if the validateWrite was called form my new form. This will allow me to skip the data change recording/stopping if the SalesLine write was called from my new form.
What is the correct approach?
I could create a boolean recordSaveChecks and set it before calling SalesLine.write(), but is there a better way?
Edit: To clarify, I do not have form specific custom verification to add, I have a system-wide verification (therefore sits on the SaleLine Table), which needs to be skipped when called from from 1 specific form.
The best option may be to move the customization that is form specific onto the form's data source rather than on the table itself. But if you true want to add form-specific code to the table, you can see an example in Tables\Address.update(), where it checks this.dataSource().formRun().name() to determine if it has been called from the relevant form.
You could put your code on the SalesLine DataSource in the ValidateWrite() method, before the super call Something like this:
ret = YourCheckGoesHere;
if(ret)
{
ret = super();
}
else
{
info("Why validation failed goes here");
}
return ret;
Then you've implemented the validation logic into Table not into the Form because you need the validation to be system wide but you need to prevent this validation when Insert/Update the record.
I think you can by override write() method of Form DataSource and use SalesLine.doInsert(); and SalesLine.doUpdate();
Related
In D365 Finance and Operations, on form TaxExempt, General Section, there are several fields like CodeType, CodeName, CompanyList (dropdown menu).
User should type in desired values (types and names).
In next section Property VAT - setup there is command button New. When clicked on that button, it should create line with values which are taken from General Section: Company(from company list selection), Sales Tax Code (from code type) and Name (from Code Name). For now, it only creates blank line. Is there some advice how this can be performed ?
The method that will accomplish your goal is the initValue on the form datasource. After the super() call, add default values from other fields located on your form. A sample might look like this:
[DataSource]
class TaxExemptCodeTable
{
/// <summary>
/// Default values from other form controls/fields on new record creation
/// </summary>
public void initValue()
{
super();
TaxExemptCodeTable.Value = CustomFormControl.text();
//etc.
}
}
If you are creating an extension, there are actually multiple events for this depending on the existing baseline code. OnInitValue would be the analogue to compare to the non-extension solution mentioned above, but if there is existing code on this it might overwrite your field if there is already defaulting logic on the formdatasource. This is because the event will fire as one of the last methods called by the framework in the super() call, but before any code placed after the super(). This complicates the extension scenario.
If this is the case, you could look into defaulting values on the OnCreated event which will fire after the previous events and "base"/"out of the box code" that might already exist on these methods and/or events. This would overwrite any existing defaulting/init logic with the values you specify in the oncreated event, while also giving you the context of the form to work with (as opposed to table level events which would not have form controls/values to use which seems mandatory for your requirements)
I am trying to build sth pretty simple, but I try to do it the correct way. But I struggle to figure out what is best.
I have a process chain where the user has to fill in some fields in different forms. Sometimes it depends from the user inputs which form the user is shown next.
[HttpGet]
public IActionResult Form1(Form1Vm f1vm)
{
return View(f1vm);
}
[HttpPost]
[ActionName("Form1")]
public IActionResult Form1Post(Form1Vm f1vm)
{
//process the data etc
//prepare the new viewmodel for the next form view (f2vm)
//Option1:
return View("Form2", f2vm);
//Option2:
return RedirectToAction("Form2", f2vm);
//for Option 2 I would need an additional HttpGet Action Method in which I
//would have to call Modelstate.Clear(); in order to not have the
//immediate validation errors on page load
//also all the properties of my viewmodel are passed as get parameters
//what looks pretty nasty for me
}
//More form views action methods should be added here...:
What is the better way? As mentioned in my comments above I have quite a big disadvantage for using the RedirectToAction option. However if I use the direct View(); call, I don't take care on https://en.wikipedia.org/wiki/Post/Redirect/Get and the user cannot simply refresh a page without getting a warning that his form is submitted once again.
Do I miss another way or don't see something obvious?
Edit: I just thought about a 3rd way, which I have seen quite often: Not transfering the whole VM to a HttpGet method but only the ID. I'd then have to load all the data stored previously directly from the db, map it again to my new VM and then call the View(); with this VM. Right now I think this is the "best" solution, however I feel like it is pretty laborious...
As per the dicussions, I would suggest using depending on your preference :
1) Save to db at the end of each form post and as you suggested use the I'd to redirect to a GET.
2) Depending on the the number of form pages and your requirements, retrieving values that a form needs on the get would be standard practice. This ensures that if a user drops off a form at any stage you can then start them off where they left off.
3) I wouldn't setup the viewmodel for the next form in the post of the previous. Generally as part of the single responsibility principle you want to ensure that your methods have only one reason to change.
4) PostRedirectGet pattern should be implemented with this to ensure data is not saved multiple times if a user refreshes after a post.
I want refresh a form from a class. I want the refreh after a insert() statemant. Is here a better solution for this problem.
Here is my code:
try {
do {
row++;
this.readRow(row, cells);
ttsbegin;
this.insert();
ttscommit;
type = cells.item(row+1, 1).value().variantType();
}
while (type != COMVariantType::VT_EMPTY);
<--------
After the while the insert is finished and at this position where I will the refresh.
Usually you would pass a reference of the form's data source you want to refresh to your class and then call research on it to refresh it so that your newly inserted records appear.
Alternatively, although IMHO not that clean, is to pass a buffer of that form's data source to your class and then (maybe after checking via isFormDataSource) access and refresh the data source via the buffer's dataSource method.
A third way would be to implement a dedicated method on your form solely for the purpose of refreshing the data source as described above. When creating the instance of your class you then pass a reference to your form so that you can call that method when needed.
Update: To see how to call a method defined on a form from a class see the class Tutorial_Apply and form Tutorial_Form_Apply which shows how to call the method applyText which is implemented on the form. Likewise, you could define a method refreshData which calls research on your data source.
Is there any way to override method in dynamic form?
I've created a form from code (create Form, adding DataSource, etc. and then FormRun). The problem is with the datasource validation. In normal form (in the AOT) I'd use return true in validateWrite to prevent normal validation on table.
How I can achieve this only from code? (or more precisely: when I've only class to play with)
I think the FormBuild.addMethod is what you are looking for. Provide the FormBuildDatasource object as the third argument to the addMethod method.
I have form with VendTable grid for example, which has CustAccount field.
I want to place button, click on which will open CustTable form where all customers are visible.
If I just put CustTable menuitem, then clicking on it will open CustTable form, but in this form only one record is displayed - one that has the same AccountNum as in vendTable.CustAccount.
How to open whole custTable? Is there better solution than create button, and then use ClassFactory::FormRunOnClient to display form?
PS. I need button, so RMB->"Go to the Main Table Form" doesn't count.
The problem is that the VendTable record is applied as an argument to the CustTable form, which then creates a dynalink. The solution is to avoid the argument.
Override the clicked method in the CustTable display menu item like this:
void clicked()
{
this.menufunction().run(new Args(element));
}
This calls the CustTable form with the caller object only and without the record argument.
I know this is a fairly old question but if someone comes here looking for the answer, just call method clearDynalinks() on the object QueryBuildDataSource.
For example, you have created a Form and it is automatically filtering your Datasource because of the Dynalinks that Dynamics creates automatically, you solve it by putting the following code inside the init() method, on your form Datasource:
QueryBuildDatasource qbds;
;
qbds = this.query().dataSourceTable(tablenum(MyTableName));
qbds.clearDynalinks();
// Next line is optional, it clears initial ranges
qbds.clearRanges();
// if you need to add any ranges you can do it right after you clear the initial dynalinks / ranges
Hope it helps...
You have 2 options, you can either create a button and override its clicked() method, or
use a MenuItemButton and assign an Action MenuItem to it.
Using MenuItems is a best practice, because it allows you to use the AX security & configuration framework. You can associate a class to the MenuItem and in the class' main() method you can run the FormRunOnClient() stuff as needed.