Customizing Microsoft Dynamics AX 2009

  • 6/10/2009

Form Customization

Like most of the elements in the AOT, forms can be customized to include additional information and actions, such as fields and buttons, and to fulfill user requirements. The design and behavior of a form are provided by a combination of the form and the tables that are bound to the form.

The best way to implement forms is to keep most of the business logic and design decisions in tables and classes, focusing only on the positioning of fields and menu items when designing the form. This approach has several advantages:

  • X++ code in forms is executed on the client tier only; X++ code in table methods can be executed on the server tier to optimize performance.

  • Customizations made to a form are restricted to that form; customizations made to a table or a class apply to all forms that use that table or class as a source of data. This results in a consistent user experience wherever the table is used.

  • When a form is customized, the entire form is copied to the current layer; customizations to tables and classes are more granular. When fields, field groups, and methods are customized, a copy of the specific element is in the current layer only. This makes upgrading to service packs and new versions easier.

  • X++ customizations to the validate, default, and database trigger methods on forms, such as create, validateWrite, and write, affect only the records that are modified through the user interface. If records are modified somewhere other than that form, then that customized form’s X++ code doesn’t execute.

The following actions be customized only on the form, not by customizing a table:

  • Enable and disable fields and other user interface elements (Enabled = Yes/No)

  • Show and hide fields and other user interface elements (Visible = Yes/No)

However, you should consider having a table or a class method determine the business logic on the form. An example of this is shown in the following lines of X++ code from the InventTable form, in which a method on the table determines whether a field can be edited.

void setItemDimEnabled()
{
    boolean     configActive    = inventTable.configActive();
    ...
    inventTable_ds.object(
             fieldnum(InventTable,StandardConfigId)).allowEdit(configActive);
    inventTable_ds.object(
             fieldnum(InventTable, StandardConfigId)).skip(!configActive);
    ...
}

By moving these decision-making methods to a table or class, you make them available to other forms.

Learning Form Fundamentals

The rich client user interface in Dynamics AX is made up of forms that are declared in metadata and often contain associated code. Ideally, you should customize these forms as changes to metadata and make any changes at the lowest level (i.e., table level rather than form level) possible to ensure the greatest amount of metadata and code reuse.

The most visible change from Dynamics AX 4.0 to Dynamics AX 2009 is the change from a predominantly multiple-document interface (MDI) to a predominantly single-document interface (SDI). Forms with a WindowType property value of Standard (the default) are now SDI forms, and the WindowType values of ListPage and ContentPage have been added to fill the Workspace content area to provide a navigation experience similar to that in Microsoft Office Outlook. The different WindowType values share the same object model, metadata, and method overrides, so form customization skills are applicable across all forms.

Customizing with Metadata

Metadata customization is preferred over code customization because metadata changes (also called deltas) are easier to merge than code changes.

When customizing forms, you should be aware of the important properties, the metadata associations, and the metadata inheritance that is being used to fully define the form and its contents.

Metadata associations

You edit the metadata in Dynamics AX by using the AOT. The base definitions for forms contained within the AOT\Forms node is composed of a hierarchy of metadata that is located in other nodes in the AOT. To fully understand a form, you should investigate the metadata associations it makes. For example, a form uses tables that are declared in the AOT\Data Dictionary\Tables node, security keys that are declared in the AOT\ Data Dictionary\Security Keys node, menu items that are declared in the AOT\Menu Items node, queries that are declared in the AOT\Queries node, and classes that are declared in the AOT\Classes node.

Metadata inheritance

You need to be aware of the inheritance within the metadata used by forms. For example, tables use Base Enums, Extended Data Types, and Configuration Key. A simple example of inheritance is that the Image properties on a MenuItemButton are inherited from the associated MenuItem if they aren’t explicitly specified on that MenuItemButton. Table 5-4 shows important examples of pieces of metadata that are inherited from associated metadata.

Table 5-4. Examples of Metadata Inheritance

Type of Metadata

Sources

Labels and HelpText

MenuItem→MenuItemButton Control

Base Enum→Extended Data Type→Table Field→Form DataSource Field→Form Control

(The Base Enum Help property is the equivalent of the HelpText property found in the other types.)

Relations

Extended Data Type→Table

Security keys

Table Field→Table→Form Control

MenuItem→MenuItemButton Control

Form→Form Control

Configuration keys

Base Enum→Extended Data Type→Table Field→Form DataSource

Field→Form Control

Image properties (e.g., NormalImage)

MenuItem→MenuItemButton Control

Inheritance also occurs within forms. Controls that are contained within other controls receive certain metadata property behaviors from their parents unless different property values are specified, including HTMLHelpFile, HTMLHelpTopic, Security Key, Configuration Key, Enabled, and the various Font properties.

Menu definitions

Dynamics AX 2009 has a number of new navigation capabilities in the form of area pages and the address bar to complement the existing navigation pane (sometimes referred to as the “WunderBar”). In terms of metadata, the area pages and address bar are mostly just additional methods of exposing the existing menu metadata defined in the AOT\Menus and AOT\Menu Items nodes. The modules are defined in AOT\ Menus\MainMenu, and you can follow the menu structure from that starting point. For example, the Accounts Receivable module is represented by the AOT\Menus\MainMenu\Cust MenuReference and is defined as AOT\Menus\Cust.

The menu metadata for list pages and content pages has some small changes. A primary list page is implemented as a submenu with IsDisplayedInContentArea=Yes, MenuItemType=Display, and MenuItemName populated. A secondary list page, a list page that adds ranges to a primary list page, is implemented as a menu item under the submenu of its primary list page. The list pages and content pages are navigation places, so all their menu item and submenu references are set to IsDisplayedInContentArea=Yes so that they appear in the Places group in the area pages and the Places section in the navigation pane. The other menu items in the root of each module’s menu definition are displayed in the Common Forms group in the area pages and in the root of the Forms section in the navigation pane.

Important metadata properties

Many properties are available to developers, but some are more important than others. Table 5-5 describes the most important form design properties, and Table 5-6 describes the most important form data source properties.

Table 5-5. Important Form Design Metadata Properties

Property

Explanation

Caption

The caption text shown in the title bar of a standard form or in the Filter Pane of a list page.

TitleDataSource

The data source information displayed in a standard form’s caption text and used to provide filter information in the caption text of a list page.

WindowType

Standard - (Default) A standard SDI form that opens as a separate window with a separate entry in the Windows taskbar.

ContentPage - A form that fills the Workspace content area.

ListPage - A special style of ContentPageused to display records in a simple way that provides quick access to filtering capabilities and actions. It requires at least an Action Pane and a Grid.

Workspace - A form that opens as an MDI window within the workspace. Workspace forms should be developer-specific forms.

Popup - A form that opens as a subform to its parent. Popup forms don’t have a separate entry in the Windows taskbar and can’t be layered with other windows.

AllowFormCompanyChange

Specifies whether the form allows company changes when used as a child form with a cross-company dynalink.

No - (Default) Form closes if parent form changes its company scope.

Yes - Form dynamically changes company scope as needed.

HTMLHelpFile

Specifies the path to the Help topic file.

HTMLHelpTopic

Specifies the topic to use from the referenced Help file.

Table 5-6. Important Form DataSource Metadata Properties

Property

Explanation

Name

Named reference for the data source. A best practice is to use the same name as the table name.

Table

Specifies the table used as the data source.

CrossCompanyAutoQuery

No - (Default) Data source gets data from the current company.

Yes - Data source gets data from all companies (e.g., retrieves customers from all companies).

JoinSource

Specifies the data source to link or join to as part of the query. For example, in the SalesTable form, SalesLine is linked to SalesTable. Data sources joined together are represented in a single query whereas links are represented as a separate query.

LinkType

Specifies the link or join type used between this data source and the data source specified in the JoinSource property. Joins are required when two data sources are displayed in the same grid. Joined data sources are represented in a single query whereas a linked data source is represented in a separate query.

Links

Delayed - (Default) A pause is inserted before linked child data sources are updated, enabling faster navigation in the parent data source because the records from the child data sources are not updated immediately. For example, the user could be scrolling past several orders without immediately seeing each order line.

Active - The child data source is updated immediately when a new record in the parent data source is selected. Continuous updates consume lots of resources.

Passive - Linked child data sources are not updated automatically. The link is established by the kernel, but the application developer must trigger the query to occur when desired by calling “ExecuteQuery” on the linked data source.

Joins

InnerJoin - Selects records from the main table that have matching records in the joined table, and vice versa. There is one record for each match. Records without related records in the other data source are eliminated from the result.

OuterJoin - Selects records from the main table whether or not they have matching records in the joined table. An outer join doesn’t require each record in the two joined tables to have a matching record.

ExistJoin - Selects a record from the main table for each matching record in the joined table.

NotExistJoin - Selects records from the main table that don’t have a match in the joined table.

InsertIfEmpty

Yes - (Default) A record is automatically created for the user if none is present.

No - The user needs to manually create the first record. This setting is often used when a special record creation process or interface is used.

Image metadata

Dynamics AX 2009 makes greater use of images and icons throughout the application to provide the user with additional visual cues. Icons are used extensively in list pages to help users identify specific actions. The metadata properties used to associate images and icons with buttons, menus (menu items), and other controls depends on their location. Table 5-7 describes the metadata properties used for the three common image locations.

Table 5-7. Image Metadata

Image Location

Explanation

Embedded

Embedded image resources are associated with buttons, menus, and other controls using the NormalResource and DisabledResource properties. These resources are compiled into the kernel and therefore you can’t add to the embedded resources list. The full list of embedded resources can be seen in the Embedded Resources form (Tools\Development Tools\Embedded Resources).

File

File image resources are associated with buttons, menus, and other controls using the NormalImage and DisabledImage properties. File image resources should be on the local computer wherever possible for performance reasons, but can be on a file share if needed.

AOT

AOT image resources cannot be utilized simply through metadata. AOT resources can be utilized only by adding code that copies the AOT resource into a temp folder and sets the NormalImage property at run time. The following code should be added into a dependable form method override (such as the Init method after the super call) for execution at run time:

SomeButton.normalImage(SysResource::getImagePath("<AOT
Resource Name>"));

Customizing with Code

You should customize forms with code only as a last resort. Customizing with metadata is much more upgrade friendly since metadata change conflicts are straight forward to resolve whereas code change conflicts need deeper investigation that sometimes involves creating a new merged method that attempts to replicate the behavior from the two original methods.

When you start to customize Dynamics AX, the following ideas may provide good starting points for investigation:

  • Leverage examples in the base Dynamics AX 2009 codebase by using the Find command on the Forms node in the AOT (Ctrl+F).

  • Refer to the system documentation entries (AOT\System Documentation) for information about system classes, tables, functions, enumerations, and other system elements that have been implemented in the AX kernel.

  • When investigating the form method call hierarchy for a suitable location to place customization code, add a debug breakpoint in the Form Init method and step through the execution of method overrides. Note that control events (e.g., clicked) do not trigger debugging breakpoints. An explicit breakpoint (i.e., "breakpoint;“) keyword is needed in the X++ code.

To enable simpler code maintenance, the following rules should be followed:

  1. Utilize the table and field functions of FieldNum (e.g., fieldnum(SalesTable, SalesId)) and TableNum (e.g., tablenum(SalesTable)) when working with form data sources.

  2. Avoid hard coding strings by using sys labels (e.g., throw error(“@SYS88659”);) and functions like FieldStr (e.g., fieldstr(SalesTable, SalesId)) and TableStr (e.g., tablestr(SalesTable)).

  3. Use as few method overrides as possible. Each additional method override has a chance of causing merge issues during future upgrades, patch applications, or code integrations.

When X++ code is executed in the scope of a form, there are some form-specific global variables created in X++ to help developers access important objects related to the form. These global variables are described in Table 5-8.

Table 5-8. Form-Specific Global X++ Variables

Variable

Use and Example

Element

Variable that provides easy access to the FormRun object in scope. Commonly used to call methods or change the design.

element.args().record().TableId == tablenum(SalesTable)

name = element.design().addControl(FormControlType::String,
"X");

DataSourceName (e.g., SalesTable)

Variable that provides easy access to the current/active record/ cursor in each data source. Commonly used to call methods or get/set properties on the current record.

if (SalesTable.type().canHaveCreditCard())

DataSourceName_DS (e.g., SalesTable_DS)

Variable that provides easy access to each data source. Commonly used to call methods or get/set properties on the data source.

SalesTable_DS.research();

DataSourceName_Q (e.g., SalesTable_Q)

Variable that provides easy access to each data source’s Query object. Commonly used to access the data source query to add ranges prior to query execution/run. Equivalent to SalesTable_ DS.query.

rangeSalesLineProjId    = salesLine1_q.dataSourceTable-
(tablenum(SalesLine)).addRange(fieldnum(SalesLine, ProjId));

rangeSalesLineProjId.value(ProjTable.ProjId);

DataSourceName_QR (e.g., SalesTable_QR)

Variable that provides easy access to each data source QueryRun object that contains a copy of the query that was most recently executed. The query inside the QueryRun object is copied during the FormDataSource ExecuteQuery method. Commonly used to access the query that was executed so that query ranges can be inspected. Equivalent to SalesTable_DS.queryRun.

SalesTableQueryBuildDataSource =
SalesTable_QR.query().dataSourceTable(tablenum(SalesTable));

ControlName (e.g., SalesTable_SalesId)

Variable created for each control set as AutoDeclaration=Yes. Commonly used to access controls not bound to a data source field, such as the fields used to implement custom filters.

backorderDate.dateValue(systemdateget());

Form method overrides allow developers to influence the form life cycle and how the form responds to some user-initiated events. The most important form method overrides are described in Table 5-9. The two most overridden form methods are Init and Run.

Table 5-9. Form Method Override Explanations

Method

Explanation

Init

Called when the form is initialized. Prior to the call to super, much of the form (FormRun) is not initialized, including the controls and the query. Commonly overridden to access the form at the earliest stage possible.

Run

Called when the form is initialized. Prior to the call to super, the form is initialized but isn’t visible to the user. Commonly overridden to make changes to form controls, layout, and cursor focus.

Close

Called when the form is being closed. Commonly overridden to release resources and save user settings and selections.

CloseOk

Called when the form is being closed via the Ok command/task, such as when the user clicks a CommandButton with a Command property of Ok. Commonly overridden on dialog forms to perform the action the user has initiated.

CloseCancel

Called when the form is being closed via the Cancel command/task, such as when the user clicks a CommandButton with a Command property of Cancel. Commonly overridden on dialog forms to clean up after the user indicates that an action should be cancelled.

CanClose

Called when the form is being closed. Commonly overridden to ensure that data is in a good state before the form is closed. Returning false aborts the close action and keeps the form open.

Form data source and form data source field method overrides allow developers to influence how the form reads and writes its data and allows developers to respond to user-initiated data-related events. The most important form data source method overrides are described in Table 5-10. The five most overridden form data source methods are Init, Active, ExecuteQuery, Write, and LinkActive.

Table 5-10. Form Data Source Method Override Explanations

Method

Explanation

Active

Called when the active/current record changes, such as when the user clicks a different record. Commonly overridden to enable and disable buttons based on whether or not they are applicable to the current record.

Create

Called when a record is being created, such as when the user presses Ctrl+N. Commonly overridden to change the user interface in response to a record creation.

Delete

Called when a record is being deleted, such as when the user presses Alt+F9. Commonly overridden to change the user interface in response to a record creation.

ExecuteQuery

Called when the data source’s query is executed, such as when the form is run (from the super of the form’s Run method) or when the user refreshes the form by pressing F5. Commonly overridden to implement the behavior of a custom filter added to the form.

Init

Called when the data source is initialized during the super of the form’s Init method. Commonly overridden to add or remove query ranges or change dynalinks.

InitValue

Called when a record is being created. Record values set in this method count as original values rather than changes. Commonly overridden to set the default values of a new record.

LeaveRecord

Called when the user is moving focus from one data source join hierarchy to another, which can happen when the user moves between controls. Commonly overridden to coordinate between data sources, but developers are encouraged to use the ValidateWrite and Write methods where possible. ValidateWrite and Write are called immediately after LeaveRecord.

LinkActive

Called when the active method in a dynalinked parent form is called. Commonly overridden to change the user interface to correspond to a different parent record (element.args().record()).

MarkChanged

Called when the marked set of records changes, such as when the user multi-selects a set of records. Commonly overridden to enable/disable buttons that work on a multi-selected (marked) set of records.

ValidateDelete

Called when the user attempts to delete a record. Commonly overridden to provide form-specific deletion event validation. Return false to abort the delete. Use the ValidateDelete table method to provide record deletion validation across all forms.

ValidateWrite

Called when the record is being saved, such as when the user presses the Close or Save buttons or clicks a field from another data source. Commonly overridden to provide form-specific write/save event validation. Return false to abort the write. Use the ValidateWrite table method to provide record write/save validation across all forms.

Write

Called when the record is being saved after validation has succeeded. Commonly overridden to perform additional form-specific write/save event logic such as updating the user interface. Use the Write table method to respond to the record write/save event across all forms.

Three commonly used form data source field method overrides are described in Table 5-11. The most overridden form data source field method is the Modified method.

Table 5-11. Form Data Source Field Method Override Explanations

Method

Explanation

Modified

Called when the value of a field changes. Commonly overridden to make a corresponding change to the user interface or to change other field values.

Lookup

Called when the Lookup button of the field is clicked. Commonly overridden to build a custom lookup form. Use the EDT.FormHelp property to provide lookup capabilities to all forms.

Validate

Called when the value of a field changes. Commonly overridden to perform form-specific validation needed prior to saving or to validate. Return false to abort the change. Use the ValidateField table method to provide field validation across all forms.

Displaying an Image

The following example illustrates how to customize the sales order form to allow a user to upload and display an image of a custom order. In this example, a customer must be able to place an order for a bike through Enterprise Portal and upload a sketch of the bike at the same time. An example of a customer-supplied bike image is shown in Figure 5-8.

Figure 5-8

Figure 5-8. Uploaded bike image

This image must be stored in the database and attached to the sales order line. Sales order lines are stored in the SalesLine table. You could add a new field to the SalesLine table of the type container and store the image in this field, but this example uses the document management functionality in Dynamics AX. The image is therefore stored in the DocuValue table with a reference to a record in the DocuRef table from the image record in DocuValue to the SalesLine record. The relationship and multiplicity among the three tables is shown in Figure 5-9.

Figure 5-9

Figure 5-9. Relationship among the SalesLine, DocuRef, and DocuValue tables

In this example, a document type named Image stores the attached file in the disk folder. The Image document type is shown in Figure 5-10. The Document Type form is located in the navigation pane, Basic\Setup\Document Management\Document Types.

Figure 5-10

Figure 5-10. Image document type

Any uploaded image is therefore stored in the document management system; a user can view the image by either clicking the Document Handling icon on the status bar or choosing Document Handling on the Command menu. The user sees the dialog box shown in Figure 5-11, in which the image can be viewed, modified, or deleted, and additional notes or documents can be attached.

Figure 5-11

Figure 5-11. Storage of the uploaded bike image in the document management system

Displaying an Image on a Form

You can display the image directly by placing it on a separate Image tab on the sales order form. Figure 5-12 shows an order for a bike with a frame size of 21 inches and a wheel size of 28 inches. The user can click the Image tab to view the uploaded bike image and confirm that it matches the ordered item before confirming the sales order. The Sales Order form (AOT\Forms\SalesTable) is located in the navigation pane, Accounts Receivable\Sales Order.

Figure 5-12

Figure 5-12. Uploaded bike image displayed on the Sales Order form Image tab

The following two example implementations describe how to use the document management tables as data sources in the form and how to create a separate method on the SalesLine table. These examples demonstrate customization of the SalesTable sales order form and the SalesLine table.

Displaying an Image by Using Joined Data Sources

One way to display the image is to apply the DocuRef and DocuValue tables as data sources for the SalesTable form. The following example creates a DocuRef data source based on the relationship among the SalesLine, DocuRef, and DocuValue tables shown in Figure 5-9. The DocuRef data source relates to the DocuRef table and is joined to the SalesLine data source. Additionally, a DocuValue data source is created to connect to the DocuRef data source. Table 5-12 shows additional properties of the data sources.

Table 5-12. DocuRef and DocuValue Property Settings

Property

DocuRef

DocuValue

Table

DocuRef

DocuValue

AllowEdit

No

No

AllowCreate

No

No

AllowDelete

No

No

JoinSource

SalesLine

DocuRef

LinkType

Active

Active

The properties JoinSource and LinkType allow the DocuRef and DocuValue records to be fetched when the user moves from one line to another. The remaining properties disable editing of the records.

You can attach multiple files, documents, and notes to a SalesLine record by using the document management feature, but the goal of this example is to display an image from a linked document named Image. You can limit the retrieved records from the DocuRef table by adding a range to the query used by the DocuRef data source. You do this by customizing the Init method on the DocuRef data source, as shown here.

public void init()
{
    super();

    docuRef_ds.query().dataSourceTable(
                       tableNum(DocuRef)).addRange(
                       fieldNum(DocuRef,TypeId)).value(queryValue('Image'));
}

This X++ code limits the query so that it retrieves only records from the DocuRef table in which the TypeId field is equal to the value ‘Image’.

The image is displayed by using a window control, which is placed in a tab control, as shown in Figure 5-13.

Figure 5-13

Figure 5-13. Tab and window controls in the SalesTable form

Although the image is stored in the File field on the DocuValue table, to display the image you can’t simply link the field as a DataField value on the window control property sheet. The image must be parsed to the control by using a method on the control in X++ that uses the FormWindowControl object. The AutoDeclaration property on the FormWindowControl object is therefore set to Yes so that the forms designer automatically declares an object handle with the same name. This handle can be used in X++ and manipulated at run time because the form application runtime automatically ensures that it is a handle to the FormWindowControl object. The Width and Height properties are set to Column width and Column height so that the image takes up all the space on the tab.

The last step is to parse the retrieved image from the DocuValue table to the BikeImage FormWindowControl object. You can do this when a DocuValue record buffer is present. This record must contain an image that is stored in the database, and the X++ code should be placed in the active method on the DocuValue data source and look like the following.

public int active()
{
    Image   image;
    int    ret;
    ret = super();
    if (docuValue.File)
    {
        image = new Image();
        image.setData(docuValue.File);
        bikeImage.image(image);
    }
    else
    {
        bikeImage.imageResource(0);
    }
    return ret;
}

This code determines whether a value exists in the File field and, if so, instantiates an image object and parses the File field value to the image object. This object is then parsed by using the Image method to the FormWindowControl object that displays the image. If the File field doesn’t contain a value, the imageResource method on the FormWindowControl object is called with a value of 0 to clear the control of any previous content. The active method is executed only if a DocuValue record has been retrieved. However, if a user moves from an order line with an image to an order line without an image, the image isn’t cleared because the active method isn’t executed. If you add the following line to the active method on the SalesLine data source, the image is cleared when a new order line becomes active and before the DocuRef and DocuValue records are retrieved.

docuBikeImage.imageResource(0);

The customizations described in this section make it possible to display the image on the Image tab. This solution has one downside, however. Whenever a user moves from one order line to another or a line is created or saved, calls are made from the client to the server, and lookups are made in the database for the DocuRef and DocuValue data sources. You can see this by turning on the client/server or SQL trace option in the Options dialog box, which you access from the Tools menu. The next section addresses this issue and offers a solution—decreasing the number of client/server calls and lookups in the database.

Displaying an Image When Activating the Image Tab

The following example implements a solution similar to the previous example, but it results in calls to the server and the database only when the image is actually displayed.

The TabPage control must be added to the SalesTable form and contain a FormWindowControl with property settings similar to those in the preceding example. The DocuRef and DocuValue tables are not, however, added as data sources for the form. Instead, this example retrieves the image—the only element shown on the Image tab—from the database only when the user chooses to display the content of the Image tab. You configure this by adding the following X++ code to the pageActivated method on the TabPage control.

public void pageActivated()
{
    Image           image;
    DocuValueFile   docuValueFile;
    ;
    docuValueFile = salesLine.bikeImage();
    if (docuValueFile)
    {
        image = new Image();
        image.setData(docuValueFile);
        bikeImage.image(image);
    }
    else
    {
        bikeImage.imageResource(0);
    }

    super();
}

This code is very similar to the code added to the DocuValue active method, but in this case the value is retrieved from a bikeImage method on the SalesLine table. The bikeImage method is a new method created on the SalesLine table with the following content.

server public DocuValueFile bikeImage()
{
    DocuRef     docuref;
    DocuValue   docuValue;
    ;
    select firstonly tableid from docuRef
        where docuRef.RefCompanyId == this.DataAreaId &&
              docuRef.RefTableId   == this.TableId    &&
              docuRef.RefRecId     == this.RecId      &&
              docuRef.TypeId       == 'Image'
    join file from docuValue
        where docuValue.RecId  == docuRef.ValueRecId;

    return docuValue.File;
}

The select statement in the bikeImage method is a combination of the two lookups in the database produced by the runtime shown in the first sample implementation, which used data sources. However, the statements in this method are joined. The bikeImage method could simply be implemented in the SalesTable form, but implementing it on the SalesLine table allows it to be reused in other forms or reports and executed on the server tier, if required.

The advantage of this implementation method is that both database lookups and calls from the client to the server are reduced by half. And because calls are made only when the Image tab is activated, they aren’t made when a user simply moves through the order lines without viewing the content of the Image tab. The disadvantage, however, is that the user can’t personalize the form or move the display of the image to another tab because retrieval of the image is dependent on activation of the Image tab.