Creating the vehicle create form with the handler class

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:

Creating the vehicle create form with the handler class

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:

Creating the vehicle create form with the handler class

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:

  1. Right-click on the Forms node in the AOT and navigate to select Dialogue, which is under New Form from template.
  2. Rename the new form to ConFMSVehicleTableCreate and drag it to our project.
  3. Ensure the form is saved.

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:

  • Make the table a global variable to the handler class and set the current record using the data source's active method (which fires whenever a record becomes active).
  • Use a 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:

  1. Right-click on the Classes node of the project and chose New | Class.
  2. Rename the class to ConFMSVehicleTableForm.
  3. Open classDeclaration and declare the vehicle table as as follows:
    class ConFMSVehicleTableForm
    {
        ConFMSVehicleTable vehicleTable;
    }
  4. Press Ctrl + S to save classDeclaration.
  5. We now need a parameter method so that we can set this variable from other objects. Pressing Ctrl + N from classDeclaration will create a new blank method.
  6. Delete the contents of the new method, type parm, and press Tab key.
  7. This opens the Create 'parm' method form. type 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;
    }

    Note

    Methods prefixed with parm are special methods in AX and produces automatic documentation. AX also understands what this method is, and in some frameworks, this naming is mandatory.

  8. We will now use a method to return the name of the create form. This is a good practice, as we could extend the form later to display a different create form for a different type of vehicle. Within the code editor, press Ctrl + N.
  9. Replace the method with the following code:
    public str createForm()
    {
        return formStr(ConFMSVehicleTableCreate);
    }
  10. Should the form not exist, the 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;
        }
    }

    Note

    The preceding code has static text (Form %1 must have method %2) that hasn't be converted into a label. This will cause a best practice deviation message.

  11. Create a label for the static text by selecting the text and then right-click on the selected text. Next, select Lookup label/text to create and paste the label back, replacing the text with a label ID.
  12. Finally, we need a constructor. We will use the 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();
    }
  13. Close the code editor and save the class.

    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.

  14. In the ConFMSVehicleTable form, open classDeclaration.
  15. Declare the ConFMSVehicleTableForm class as formHandler:
    public class FormRun extends ObjectRun
    {
        ConFMSVehicleTableForm formHandler;
    }
  16. Close the code editor and right-click on the form's Methods node and choose Override method | init. This opens the following code window:
    public void init()
    {
        super();
    }
  17. The super call handles the standard form initialization, so it must not be removed. After the super call, construct the form handler as follows:
        formHandler = ConFMSVehicleTableForm::construct();
  18. Close the code editor and expand Data Sources and the ConFMSVehicleTable data source.
  19. Right-click on the Methods node and choose Override method | active.
  20. The 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;
    }

    Note

    Remember that the form auto declares the ConFMSVehicleTable table as ConFMSVehicleTable. After the super call, we can be sure that this contains the data source's active record.

  21. Close the code editor and override the method create. This is called when the user tries to create a new record, and we must intercept the process here so that we can interact with the form's data source.
  22. The following code will be very similar each time we use a create form. The only big difference is that we are allowing 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();
            }
        }
    }
  23. Close all code editors and save all changes to the class and form.

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:

  1. Add ConFMSVehicleTable as a data source.
  2. Complete the form design.
  3. Write the createForType method, which the form handler class needs to call.
  4. Declare the form handler and initialize it from passed 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:

  1. Open the form level classDeclaration method and declare ConFMSVehicleTableForm as formHandler.
  2. Override the form level method init.
  3. Set formHandler to element.args().caller():
    formHandler = element.args().caller();
  4. Drag the ConFMSVehicleTable table from Tables onto the Data Sources node.

    Note

    As this is a create only form, we need to disable the research and reread methods (which happens when the user presses F5 or Ctrl + F5),

  5. Override the reread and research methods in Data Sources | ConFMSVehicleTable and comment on the super() call. Here's an example:
    public void reread()
    {
    //    super();
    }
  6. Override the data source method 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);
        }
    }
  7. Create a new form level method. Right-click on ConFMSVehicleTable | Methods and choose New method.
  8. Normally, we only need to call the data source create method, which we could have done by overriding the run method. Creating a table that takes part in the inheritance needs to be done by calling the data source's createTypes method, which needs to be told the type in the form of a map object.
  9. Therefore, the 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);
    }

    Note

    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.

  10. We now need to complete the design. Complete this in the same way we created the ConFMSVehicleTable details form. However, this time add the field groups to the DialogContent group control. The field groups Identification and Details will suffice.
  11. Once complete, save your changes.

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:

  1. Navigate through the design's action pane ActionPaneHeader | HeaderHomeTab | NewGroup.
  2. Delete the menu item button NewButton.
  3. Right-click on NewGroup and choose New Control | CommandButton.
  4. Rename the new control from CommandButton to cmdNewButton.
  5. Change the button's Command property to New: 260 (you need to use the drop-down list to set this). Here, 260 refers the command number.
  6. To see the progress, open the form.
  7. This isn't in keeping with other controls, so we need to change more properties on the cmdNewButton control.
  8. Set NormalImage to 10874.
  9. Set ImageLocation to EmbeddedResource.
  10. Change Big to Yes.
  11. Save the form changes and test the form.

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.163.197