Thomas's profileThomas Widmer's AX BlogBlogLists Tools Help

Thomas Widmer

Occupation
Location
View Thomas Widmer's profile on LinkedIn

Thomas Widmer's AX Blog

6/26/2009

Dialogs

I read in some other blog how to apply customer methods such as lookup() to a standard dialog. The trick is to apply a Field Identifier to the dialog field, and then create a method that follows the naming convention so it will be found at runtime. See the example below:

A)

class myClass extends RunBase
{
    Dialog              dialog;
    #define.FID_OPPORTUNITYID(903)
}

// Add fields to the dialog
protected Object dialog(DialogRunbase _dialog, boolean _forceOnClient)
{
    DialogRunbase  dialog = super();
    DialogGroup     dialogGroup;
    ;
    dialogGroup = dialog.addGroup("@SYS102068");
    dlgFldOpportunityId = new DialogField(dialog, typeid(smmOpportunityId), #FID_OPPORTUNITYID);
    dialog.addCtrlDialogField(dlgFldOpportunityId.name());
    dlgFldOpportunityId.init(dialog);
    dlgFldOpportunityId.value(opportunityId);
    return dialog;
}

//prefiltered lookup for opportunityId
private void fld903_1_lookup()
{
    FormStringControl control = dialog.formRun().controlCallingMethod();
    SysTableLookup    sysTableLookup = SysTableLookup::newParameters(tablenum(smmOpportunityTable), control);
    Query             query = new Query() ;
    QueryBuildDataSource    qbds;
    ;
    sysTableLookup.addLookupfield(fieldnum(smmOpportunityTable, OpportunityId));
    sysTableLookup.addLookupfield(fieldnum(smmOpportunityTable, Subject));
    qbds = query.addDataSource(tablenum(smmOpportunityTable));
    if(partyId)
        SysQuery::findOrCreateRange(qbds,fieldnum(smmOpportunityTable, PartyId)).value(partyId);
    sysTableLookup.parmQuery(query);
    sysTableLookup.performFormLookup();
}

This is very neat if you need to enhance only one special field, however I quickly noticed, that when you start improving the user interface, you will add the next function and the next and so on. Then the trick above is not the way to go. It is much easier to add a customer dialog as follows:

B)
class myClass extends RunBase
{
    Dialog              dialog;
}

protected Object dialog(DialogRunbase _dialog, boolean _forceOnClient)
{
    DialogGroup dialogGroup;
    ;
    //dialog = super();
    dialog = Dialog::newFormnameRunbase(formstr(CustomDialog),this);
    dialog.caption(Runbase::getDescription(classidget(this)));
    dialogGroup = dialog.addGroup("@SYS102068");
    //add custom fields, without special functionality

    return dialog;
}

The corresponding form needs to have the following groups and buttons to work. In addition to those groups you can add various other fields, datasources etc. with all the handlers - such as lookup() - you like. You can pass the results by extracting myClass with element.args().caller().runbase() and implementing some parm methods on myClass, which you can access in edit methods.

CustomDialog

//edit methods can be drag and dropped into the design section
edit ItemId itemId(boolean _set, ItemId _itemId)
{
    ItemId ret;
    ;
    if(_set)
    {
        myClass.parmItemId(_itemId);
    }
    ret = myClass.parmItemId();
    return ret;
}

Thomas

6/25/2009

Talking about Developing a new Workflow

  Nice description of workflows by Sumit:

Quote

Developing a new Workflow

The section below describes briefly, the process of developing a new workflow.

 

To develop a new workflow, following artifacts or objects need to be created / modified:

·         Workflow Categories

·         Workflow Templates

·         Workflow Query (Document)

·         Workflow Approvals and Tasks (Tasks are optional)

·         Enabling the workflows on the form

·         Workflow submission classes

Let us go through each of these artifacts one by one:

 

Create Workflow Categories:

A workflow category defines the module in which the workflow will be available. Modules are defined by the SysModule enum. You will be doing following here:

·         Create a new category in workflow categories node (AOT à Workflow à Workflow Categories)

·         Specify the name and module to which it belongs

 

Create Workflow Templates:

A workflow template brings all the different elements of the workflow together. Workflow configurations are created based on a template, and many configurations can be based on the same template. The template defines which actions are allowed and which are required.

You will be doing following here:

·         Create a new template in the workflow templates node (AOT à Workflow à Workflow Templates)

·         Specify a name and category to which it belongs

 

Next we create a work flow document.

 

Create Workflow Document:

A query defines what tables are used to determine that a workflow can be initiated. Here you will do the following

·         Create a new query (AOT à Queries)

·         Specify a name and Add required tables to the data source.

·         Create a new class extending from WorkFlowDocument class (AOT à Classes)

·         Override the method getQueryName and return the name of newly created query

·         Save the class

·         Once query and its supporting class is created, attach this to Workflow template (Specify it under the Document property of template)

 

Next step is to attach Approvals and / or Tasks

 

Create Workflow Approvals (or Tasks): An approval route may contain a number of outcomes. It may be approved, rejected, returned or a change may be requested (For task it will be complete, reject or request change). The Workflow Approval element determines which of these outcomes is allowed and what happens in the event of each outcome. Each outcome can trigger specific code by specifying a menu item for each item. Do the following to create an approval

·         Create a new approval object in the Approvals node. (AOT à Workflow à Approvals)

·         Specify a unique name for the approval

·         Specify the Document (The class created for query) in the Document property

·         Specify ParticipantProvider. Normally you specify WorkflowUserGroupParticipantProvider. But one can create their own participant providers similar to the standard one and use them

·         Specify DueDateProvider. Normally you specify WorkflowWorkCalendarDueDateProvider. But one can create their own participant providers similar to the standard one and use them

·         Specify HierarchyProvider. Normally you specify WorkflowLimitHierarchyProvider. But one can create their own participant providers similar to the standard one and use them

·         Set the DocumentMenuItem to form menu item where you want the workflow to appear (Example sales order etc.)

·         Approval Outcomes:

o   Use a standard class that acts as an engine for all approval outcomes

o   You are not required to do anything but set the workflow to applicable outcomes, therefore call the same class from different menu items. The menu items simply allow you to use two different labels. In more complex workflows it may be necessary to override or copy and modify this class rather than use it directly. Ex: If you have to set an outcome for Approved do the following:

1.       Create a new Action type menu item and specify a name to it

2.       Set ObjectType property to Class and Object property to WorkflowWorkItemActionManager

3.       Now get to Workflow à Approvals à <Approval you created> à Outcomes à Approve node and specify ActionMenuItem as new menuitem created before.

o   Repeat Step 2 for all the outcomes you need

o   If you do not need an outcome you can simply disable it by changing Enabled property to No

·         Once an approval (or task) is created, drag and drop this approval (or task) into the required workflow template

 

Enable workflow on a form:

Now that the workflow template is defined, you can specify which forms will use this template. Do the following:

·         Add a WorkflowState field (More can be added by CreatedBy, Time, Status etc.) to the required table

·         Then traverse to the required form and on the design node properties, Set property WorkFlowEnabled to Yes and Set the WorkFlowDataSource (Will be the table for which you created the above field).

·         If you want, you can override the method canSubmitToWorkflow on the form and specify conditions when user can submit the record to workflow

 

After enabling the workflow on class, create a class to submit the records to workflow

 

Create a Submit to Workflow class:

To submit a document to workflow, call standard code to prompt the user for a comment and to process the submission. (Look at purchReqWorkflow class for an example)

·         After creating the submit to workflow class, create a corresponding action menu item

·         Now traverse to the workflow template and specify this submit to workflow class menu item on the SubmitToWorkFlowMenuItem property

·         Once this is done you can then configure the workflow and test it. (You can refer the Workflows – Installation and Configuration document that I sent earlier for configuring the workflows)

 

This will give you a broad idea as to what needs to be done for creating a workflow.

5/13/2009

Links in Infolog

Just a short reminder for myself.

If you want your info text ( or warning or error) to link to a record, then you can just add a SysInfoAction_TableField class to the call:

info(“info text”, “help text”, SysInfoAction_TableField::newBuffer(salesTable));

But if there is the link is not defined by the record e.g. in the case of returns, you need to use the SysInfoAction_FormRun class:

SysInfoAction_FormRun sysInfoAction_FormRun;
;
sysInfoAction_FormRun = SysInfoAction_FormRun::newFormname(formstr(ReturnTable));
sysInfoAction_FormRun.parmCallerBuffer(salesTable);
info(“info text”, “help text”, sysInfoAction_FormRun);

11/21/2008

Multilingual Records using Label editor

Situation

As I needed multilingual data on a custom table I tried to identify if Microsoft has a best practice it applies to this requirement. I had to find out that multilingual data is handled differently in every instance I identified.

The application itself (forms, buttons, instructions, help etc.) is truly multilingual and is handled in a uniform throughout AX. On the other hand, record data mostly cannot be saved in different languages. At the most important places Microsoft has recognised this and has added the possibility of adding multilingual data.

Below are all the instances of multilingual texts I have identified:

Label files (application text):

This is still pretty straight forward: Translations of application text is handled in one location (the label files). There is one editor for editing the label files.

  • X++: The label code is in quotation marks (e.g. "@SYS1234") and the texts can be edited in the label editor using context menu "Lookup Label/Text"
    LabelEditor
  • AOT object properties: Field showing the standard language, when the field is selected the label code and "..." are displayed. A click on the three dots leads you to the label editor as above.
    AOT Label 
  • The Rename Item Dimension dialogue is the exception to the rule. The dialogue bypasses the label editor, allowing the user to select a language and then edit multiple labels at a time.
    ItemDim
Separate tables (multilingual data):

Data is always handled in separate tables and never using label files. I guess this is because you do not want the label files to grow indefinitely as more and more data is added. I wonder however if it could have been an option to use labels for a relatively limited table such as the unit table.

  • Item text: Translations for the item text are saved in the InventTxt table. The item text is NOT saved on the InventTable, the default value that is displayed corresponds to the company language. If you change the company language or you share the table over multiple companies with different languages, the default text might be missing. The foreign key linking the InventTxt table to the InventTable is the ItemId.
    InventTxt
  • Unit text: Translations of the unit text are saved in the UnitTxt table, the default text is however saved on the Units table. The foreign key linking the UnitTxt table to the Unit table is the UnitId.
    Even though there is essentially no difference in the table definitions of UnitTxt and InventTxt, the user experience is not the same.
    Units
  • Workflow instructions: The multilingual instructions of Workflows are again implemented in a different way: The WorkFlowMessageTxt table is a bit more generic than the previous examples by using the TableId and RecordId to link to the source table. In addition the Multilanguage entry form allows the use of variables in the text. Just as is the case with the Item text, the Message text is not save on the base table at all. The system first tries to fetch the users language, if not found, then it tries to fetch the company language. The problem of the item text remains: if you change the company language or you share the table over multiple companies with different languages then the default text might be missing.
    WorkflowText 

 

Suggestion for Microsoft

Just as Microsoft has added the UtcTimeDate type that supplies clever editing of date and time, Microsoft should add a new datatype for multilingual strings ("MLString"), that supplies the interface to edit, save and display the translations.
I would like to see the following properties:

  • Default value when used in forms and reports: Try to find a language specific value in the following order 1. text in users language, 2. text in variant of users language (e.g. en-us instead of en-au), 3. text in company language, 4. text in variant of company language (e.g. de instead of de-ch) 5. any language
  • Possibility of adding a second data field or a static text to define a specific language. Optional: If this language is not found, then follow the rules above.
  • One consistent way of editing the translations. Suggestion: use the ItemName type of editing.
  • Transparent management of the saved translations; Suggestion: A table contains all translations with the key TranslationId (preferably Int64 instead of a string) and the LanguageId. Maybe the TableId of the referencing table could be added to allow horizontal partitioning if the table grew too large. All that needs to be saved in the referring table is the TranslationId.

 


UtcTimeDate:
UtcDateTime