The Create
form is normally used when key data required to create the record is scattered through the details form. In our case, this isn't a mandatory step, given the data is easily accessible within the details form. However, it is included in order to complete the full pattern.
Our case has a little more to offer, as our vehicle table takes part in the inheritance. AX provides functionality at the kernel level to help us. Try creating a new vehicle from the list page created earlier. Once we click on New vehicle, the following dialog box is shown:
AX uses the metadata stored in the data dictionary to determine how many types of vehicles we have and also determine the label to display. It knows this from the inheritance we configured in Chapter 2, Creating Data Structures. The label is the table's label.
When we click create, an empty vehicle details form is shown, where we can complete the vehicle.
The dialog is not tied to the list page's New vehicle button but is actually triggered from the data source's create method on the ConFMSVehicleTable
details form. The event occurs before the form is displayed, and therefore, it appears as though the dialog is created by the list page. This happens because the menu item referenced by the new button has OpenMode set to New.
To demonstrate this pattern, we will create a dialog whose purpose is to create a vehicle record. This means we can organize the form differently. This is very useful for more complicated forms, as we can place the key data in one place, making the form easier to use.
In order to do this, we need to tie our new Create
form into the Details
form's record create process. We do this by intercepting the standard process and returning the new record back to the form.
The process we will implement is shown in the following diagram:
We need to create two objects, the handler class and the create form. As both depend on each other, we will create both objects at the same time.
First, we will create a blank create form, as we need this to finish the handler class; we will finish the form later. We will name this as the details form suffixed with Create
. This places the form next to the details form in the AOT and describes the object sufficiently.
To do this, follow these steps:
ConFMSVehicleTableCreate
and drag it to our project.First, we will create a basic form handler class. Unlike the list page interaction, there is no natural framework to hook into. This means, we will hook the class to the events manually, as required.
There are two ways of hooking up the interaction:
FormDataSource
object (again, a global variable to the class) as the connection to the form data source. Once set (during form initialization), the current record is always available from the data source.The most common method used by Microsoft is the first option because it is easier to follow.
To create the initial part of the form handler class, follow these steps:
ConFMSVehicleTableForm
.class ConFMSVehicleTableForm { ConFMSVehicleTable vehicleTable; }
parm
, and press Tab key.ConFMSVehicleTable
in the Type field. In the Name field, enter vehicleTable
(as we did in the class declaration), and click on OK. This will create a parameter method as follows:public ConFMSVehicleTable parmVehicleTable( ConFMSVehicleTable _vehicleTable = vehicleTable) { vehicleTable = _vehicleTable; return vehicleTable; }
public str createForm() { return formStr(ConFMSVehicleTableCreate); }
formStr
command will cause the compiler to produce a compilation error. Now, press Ctrl + N again to write the method that will construct and run the form. The following method will do this:public boolean create(RefRecId _instanceRelationType) { Args args = new Args(); FormRun createFormRun; ConFMSVehicleTable currentRecord; // We need to call a method on the form that will // tell its data source to create a new record. // Since the method doesn't exist on the base // class FormRun we can't use it. // Assigning FormRun to an object of type Object // lets us call methods that the compiler // can't resolve. Object createFormRunObj; currentRecord = vehicleTable; // args is a standard object (similar to a contract) // that is used to construct a form. args.name(this.createForm()); args.caller(this); // The classFactory global class is used to construct // the form from the args object. createFormRun = classFactory.formRunClass(args); // initialise and run the form createFormRun.init(); createFormRun.run(); // We need to tell the create form's datasource to // create a record, since we are using inheritance // we need to tell which type we are creating. createFormRunObj = createFormRun; // we must check the method exists, and throw an // appropriate error if not. if(!formHasMethod(createFormRun, "createForType")) { throw error( strFmt("Form %1 must have method %2", this.createForm(), "createForType")); } // strFmt is used to format strings, allowing numbered // placeholders that are replaced at runtime. // In this case it will replace %1 with the value // returned from createForm() and %2 from // 'createforType'. createFormRunObj.createForType(_instanceRelationType); if (!createFormRun.closed()) createFormRun.wait(); //If the user presses OK, success! if (createFormRun.closedOk()) { return true; } else { // user cancelled, so set the vehicleTable record // back to the previous record the form will then // call parmVehicleTable to get this record back // as the current record vehicleTable = currentRecord; return false; } }
construct
code snippet for this. Press Ctrl + N and remove the new method contents, type construct
, and press Tab. This should create a method as follows:public static ConFMSVehicleTableForm construct() { return new ConFMSVehicleTableForm(); }
The next task is to hook the handler class to the vehicle details form, where we need to initialize the class, maintain the current record via the active method, and override the data source's create method to call our new method.
ConFMSVehicleTable
form, open classDeclaration.ConFMSVehicleTableForm
class as formHandler
:public class FormRun extends ObjectRun { ConFMSVehicleTableForm formHandler; }
public void init() { super(); }
formHandler = ConFMSVehicleTableForm::construct();
ConFMSVehicleTable
data source.active
method is called whenever a record becomes active (which is also when we change records). Here, we need to update the vehicle record in our handler class. To do this, write the following code after the super call:public int active() { int ret; ret = super(); formHandler.parmVehicleTable(ConFMSVehicleTable); return ret; }
super()
be called in order to determine the Vehicle type
option displayed to the user.public void create(boolean _append = false) { ConFMSVehicleTable newVehicleTable; RefRecId instanceRelationType; // start: inherited table code // we need to let the create happen so the user can // choose the type super(_append); // store the type the user selected and remove the // record that was create above. instanceRelationType = ConFMSVehicleTable.InstanceRelationType; ConFMSVehicleTable_ds.delete(); // end: inherited table code // call the create form if (formHandler.create(instanceRelationType)) { // seems to be successful, but check // to see if the vehicle is created newVehicleTable = formHandler.parmVehicleTable(); if (newVehicleTable) { super(_append); ConFMSVehicleTable.data(newVehicleTable); // research the current record set above from // the database, ensure we have the correct // data loaded. // The parameter controls whether the current // position is retained ConFMSVehicleTable_DS.research(true); } } else { // if new was called from a different form // (for example, the list page) close this form if a // record wasn't created. if (TradeFormHelper::isCalledFromForm( element.args())) { // close the form - since this method is called // before the form is drawn, it won't be shown // to the user. element.close(); } } }
You may be surprised that we used a static method from the TradeFormHelper
class. This is actually not specific to the trade module, and the isCalledFromForm
method is very useful in this situation.
You can try and test this now. It will fail, but you will see from the error that the code is executing correctly. The error we are expecting is as follows:
Form ConFMSVehicleTableCreate
must have method createForType
.
We need to complete ConFMSVehicleTableCreate
. The tasks here are as follows:
ConFMSVehicleTable
as a data source.createForType
method, which the form handler class needs to call.args
object.In ConFMSVehicleTabeForm.create
, we constructed an args
object and set the caller property to the this
instance of the class. When classFactory
created the FormRun
instance for us, it copied the args
object to the form.
We can then access the args
object from within the form. There is a special object created that is constructed from the form definition; this is called element and it provides access to many parts of the form's instance, including the args
object.
To complete these tasks, please follow these steps on the ConFMSVehicleTableCreate
form:
ConFMSVehicleTableForm
as formHandler
.init
.formHandler
to element.args().caller()
:formHandler = element.args().caller();
ConFMSVehicleTable
table from Tables onto the Data Sources node.reread
and research
methods in Data Sources | ConFMSVehicleTable and comment on the super()
call. Here's an example:public void reread() { // super(); }
write
. We only need to save the record if the user presses OK. Then, we pass this new record back to the handler class. This is done using the following code:void write() { // if the user didn't press OK, or the record exists, // don't save the record. if (element.closedOk() && ConFMSVehicleTable.RecId == 0) { ConFMSVehicleTable.insert(); formHandler.parmVehicleTable(ConFMSVehicleTable); } }
createTypes
method, which needs to be told the type in the form of a map object.createForType
method needs to construct the map and call ConFMSVehicleTable_DS.createTypes
:public void createForType(RefRecId _instanceRelationType) { Map tableType = new Map(Types::String, Types::String); tableType.insert(ConFMSVehicleTable_DS.name(), tableId2name(tableNum(ConFMSVehicleTable))); ConFMSVehicleTable_DS.createTypes(tableType); }
A map is a typed collection with a unique key and a value. In our case, it holds a single item with the data source name and table name of the table to be created. The method is named createTypes
, as ConFMSVehicleTable
is a type of table. If you look under the Derived Data Sources node, which sits under the ConFMSVehicleTable
data source node, you can see all tables (types) that inherit from the main table. To create a table of one of these types, you must use the appropriate table name.
ConFMSVehicleTable
details form. However, this time add the field groups to the DialogContent group control. The field groups Identification
and Details
will suffice.You can test this now by opening the list page and pressing New vehicle. You will be asked for the type. Then, the create form is displayed. If you press OK, the details form is shown with the new record. If you press Cancel, you return to the list page.
Should we press NewButton on the details page, we get an error. We should delete this button and add CommandButton
with the command New
instead.
To do this, perform these steps on the ConFMSVehicleTable
form:
CommandButton
to cmdNewButton
.New: 260
(you need to use the drop-down list to set this). Here, 260
refers the command number.cmdNewButton
control.10874
.EmbeddedResource
.Yes
.The button appears with a new icon and is large like the rest. Clicking on New triggers the data source create method (which is why the create form is shown). This is because we set the command to New on the command button.
What this actually does is call the task
method on the form with a parameter to trigger the current data source's create
method.
3.137.163.197