Pages

Showing posts with label Dynamics AX 2009. Show all posts
Showing posts with label Dynamics AX 2009. Show all posts

Thursday, February 27, 2014

Disable auto complete on a form control

As a pattern, Dynamics AX has an auto complete feature in form controls.

There is a way to disable it though, programmatically.


There's the delAutoCompleteString method on the FormRun class. The name is pretty straight-forward.


We must override the control's textChange method to call it:

public void textChange()
{
    super();

    // Since we're overriding a control's method, "this" means the instance of the control.
    // We could also reference it by its name, if the AutoDeclaration property is Yes.
    element.delAutoCompleteString(this);
}


With that single line of code, we can disable the auto complete feature.

Monday, August 12, 2013

Displaying a SysInfoAction button on the Infolog

Actioning it

Have you ever found yourself in a situation where the Infolog keeps throwing up the same error / warning at you and you have no idea how to resolve it?
For example: "The field Foo must be set to Y to perform this action". That's a pretty clear message: you just have to set the Foo field to Y and you're good to proceed with what you were doing, except that you have no idea where the Foo field is! When I started working with AX, I found myself in this situation more than I'd like to, and it always annoyed me to have to call someone and ask where the damn Foo field was.

But sometimes, the Infolog would show me a magic button that would take me exactly to wherever I had to go to fix the error it was showing me. That magic button is called a SysInfoAction. For the example I have given above, if the good guy programmer had put a SysInfoAction on the Infolog, you could just click on a button and a form with the Foo field would pop up.


So how do you do it? Well, there are some different SysInfoAction types. Just to list a few of them:

  • SysInfoAction_CostSheetValidate
  • SysInfoAction_Editor
  • SysInfoAction_Formrun
  • SysInfoAction_MenuFunction
  • SysInfoAction_newWindow
  • SysInfoAction_Properties
  • SysInfoAction_Showplan
  • SysInfoAction_TableField


As you'd expect, each one of these classes exposes a different behavior when the user interacts with the button on the Infolog.

Let's take a look at the SysInfoAction_MenuFunction for example.

It does what the name suggests: adds a menu item on the Infolog. And it can be of any type: Action, Display or Output.

Suppose we want to redirect the user to a form so that he can fill in the "Foo" field:

SysInfoAction_MenuFunction sysInfoAction;

sysInfoAction = SysInfoAction_MenuFunction::newMenuItem(menuitemDisplayStr(FooFormMenuItem), MenuItemType::Display);

warning('The field Foo must be set to Y to perform this action', '', sysInfoAction);

Just by creating that object and passing it to the warning() method, you made the user's life a lot easier. There's also a constructor available to the SysInfoAction_MenuFunction where you can pass in the control's name of the form you're opening, and the framework will set the focus to that control if it's found on the form. Although it helps even more, the call to the method should be ugly, and easy to break, since there is no compile-time checking if the control actually exists on the form.
The good part though is that if the control isn't found, it just doesn't do anything.

But what about security?

I have to say that the security framework team did a tremendous job overall, and it's just the same with the SysInfoAction feature.
The SysInfoAction, in this case, is nothing more than a menu item. Menu items are bound with security objects in various ways, so it's kind of easy to get the related roles or duties. That being said, guess what happens if the user doesn't have the permission to run the MenuItem you have added to the SysInfoAction? The user doesn't see the button.
So if you're worrying that you might accidentally redirect a user to an important parameters form which he shouldn't have access to, don't worry, because you wont: the security framework will take care of that for you.

That all being said, I'd advise to always use SysInfoActions where it really improves the user experience.

Tuesday, February 7, 2012

Dynamics AX Custom Lookup

As a Dynamics AX developer, you'll often have to perform custom lookups. Meaning that the user may only select records in another table depending on some condition on the form, or elsewhere.


There are two ways of creating custom lookups. I'll show the easiest here, and maybe show the other way in the future.

So if you're thinking you'll have to create a new form and all that, forget it, you won't. All you have to do is write some pieces of code.



First let's create our lookup method directly on the table. You can check other tables for these special lookup methods. They appear in lots of tables. Take for example the table ContactPerson. It has the following custom lookup methods by default:

  • lookupActionContactPerson
  • lookupCustContactPerson
  • lookupDirContactPerson
  • lookupDirContactPersionId
  • lookupVendContactPerson



Now, to create a custom lookup, we'll use the class SysTableLookup as it provides us a set of methods to do so.


Here's our method, to be created directly on the table DummyTable, with some dummy data and names.

public client static void lookupDummyTable(FormStringControl _ctrl,
                                           MyFilterEDT _filter)
{
  
    // Intantiantes a SysTableLookup object telling it which control activated the lookup, and
    // what table should be displayed inside the lookup form.
    SysTableLookup sysTableLookup = SysTableLookup::newParameters(tablenum(DummyTable), _ctrl);

    Query query = new Query(); // This query will be used by the lookup form.
    QueryBuildDataSource qbds;

    ;

    // The "addLookupField" method adds the the fields that will be shown inside the lookup's grid. 
    // The second, boolean parameter indicates that this field will be returned from the lookup when a
    // record is selected. In this case, we'll return our DummyPK.
    sysTableLookup.addLookupField(fieldnum(DummyTable, DummyPK), true); 
    sysTableLookup.addLookupField(fieldnum(DummyTable, Field1));
    sysTableLookup.addLookupField(fieldnum(DummyTable, Field2));
    sysTableLookup.addLookupField(fieldnum(DummyTable, Field3));


    // Using our dummy table as a DataSource Table.
    qbds = query.addDataSource(tablenum(DummyTable));

    // This is the key part, what we want to filter to be displayed in the grid.
    qbds.addRange(fieldnum(DummyTable, MyFilterColumn)).value(_filter);


    // Passes the query to the sysTableLookup.
    sysTableLookup.parmQuery(query);

    // Activates the lookup.
    sysTableLookup.performFormLookup();
}



With that little piece of code, Dynamics AX will already display a lookup form with the table you've specified as a DataSource, displaying the fields you specified with the method addLookupField and following the results criteria of the Query you've passed to it.



Now all we have to do is actually call our lookup method, by overriding the lookup of the correct field in our Form's Data Source:


public void lookup(FormControl _formControl, str _filterStr)
{
    ;

    DummyTable::lookupDummyTable(_formControl, "SomeFilter");
}


And that's it! The lookup will be performed. But there's one additional step we'll have to take here...

When the user tries to select our DummyTable reference, Dynamics AX will display the results of the query, based on the filter we've created. So the user will only be able to see what we want him to see, and so he'll only be able to select what we want. The problem is that if he types in an existing record for the DummyTable in the control, even if does not get shown in our lookup, Dynamics AX will accept that. Remember: we're only limiting what the user may see and select for that field, but we're not limiting what he can actually type in the control. So if we don't validate it somehow, Dynamics AX will run the standard validation, which will only check if a record exists for that EDT and so on. To avoid that the user does in fact type in something we don't want, we'll have to override the validate method for our field in our Data Source. The simplest logic is to check if the DummyTable record the user typed in does attend the filter we've specified:


public boolean validate()
{
    DummyTable    dummyTable;

    ;

    dummyTable = DummyTable::find(currentRecord.DummyTableRefField);

    return dummyTable.MyFilterColumn == "SomeFilter";
    
}



So if the user simply types in for a record that does not attend our filter, Dynamics AX won't let the user save that value.



This is the easiest way to effectively implement a custom lookup and validate the user input in case he doesn't select the record from the lookup. For this example I used a literal filter, a string. But in most cases, you'll have to filter according to what the user selected for another field in your form. Things should usually be dynamic.

Hope this helps...

Thursday, February 2, 2012

Refresh DataSource and retain position

Update: Also read "How to effectively refresh data on forms".

Refresh a DataSource is a very common task to Dynamics AX developers, as you'll most likely perform changes on a record and have to present it to the user, on a form.


The most commonly used method to refresh the DataSource to make a form show what you have changed on a record is the research method.
The only problem is that the research method doesn't retain the record that was selected, it positions the DataSource on the first record of the result, causing the user to see another record. To resolve this matter, the research method, only in Dynamics AX 2009, has a boolean parameter, retainPosition. Now if you call it by passing true to it, it will sure retain the position of the selected record after the refresh, right? Wrong...


This should only work when your form has DataSources linked by Inner Joins in it, what means that the generated query has Inner Joins in it. Having any other link types or temporary tables causes it to not retain the position, even if you pass true when calling it.

So I'll present you two ways of retaining the position of the selected record, one that works sometimes, and one that always works, although you should always try and see if it works with research(true) before trying anything else.


On the first way, you could simply get the position of the DataSource before calling research(), like this:

int position;

    ;

    position = myTable_ds.getPosition();
    myTable_ds.research();
    myTable_ds.setPosition(position);

But does this work perfectly? Not again...

The code above should select the record that was on the previously selected record's position. What does that mean? It means that if your query result changes the record's orders, you won't have the correct record selected. This could easily happen if your form handles the records' order in a special way, or if the action is performed when the record or any records are inserted, which will certainly cause the the form to reorder the records when you tell it to refresh the DataSource.


The second way to do it will actually look for the correct record and then select it, so you can imagine it will perform a little worse than the others, but at least it will never fail (unless you, somehow, delete the record you're looking for in the meantime).


Simply copy the RecId from the record you want to keep selected to a new table buffer, like this:


MyTable      myTableHolder;
    
    ;

    myTableHolder.RecId = myTableRecord.RecId;
    
    myTable_ds.research(); // or: myTableRecord.dataSource().research();

    myTable_ds.findRecord(myTableHolder); // or: myTableRecord.dataSource.findRecord(myTableHolder);     

We copy if to a new table buffer because the findRecord method expects a parameter of type Common. If you stored the RecId in a variable, you would have to pass it to a table buffer anyway...


And that's it, it'll first refresh the DataSource, and then look for your record and select it, so the screen might even "blink" for a second. As I said, this is not the best when thinking about performance, but at least it works...

Friday, December 23, 2011

Post or Validate Invent Journal

When you want to post an Invent Journal through code, you can use the class InventJournalCheckPost, like this:



static void Job1(Args _args)
{

    InventJournalCheckPost      inventJournalCheckPost;
    InventJournalTable          inventJournalTable;
    ;


    inventJournalTable = InventJournalTable::find("MyJournalID");

    
    inventJournalCheckPost = InventJournalCheckPost::newPostJournal(inventJournalTable);
    inventJournalCheckPost.parmThrowCheckFailed(true);

    try
    {

        inventJournalCheckPost.run();
    }
    catch
    {
        info("An error has occurred, journal not posted.");
    }
}


When setting parmThrowCheckFailed to true, when the journal fails to be posted by any reason, Dynamics AX will throw an error with the error message. If you don't, it will simply show the error message on the infolog, but, technically, you wont know wether it posted successfully or not, so it depends on what you're trying to do. This is specially useful when you're working with transactions and depend on the journal being posted successfully to continue with your code or to abort everything.



If you're simply trying to Validate (Check) an Invent Journal, all you have to do is change which constructor you'll use for the class InventJournalCheckPost, like this:


static void Job1(Args _args)
{

    InventJournalCheckPost      inventJournalCheckPost;
    InventJournalTable          inventJournalTable;
    ;


    inventJournalTable = InventJournalTable::find("MyJournalID");

    // Constructor to just check the journal:
    inventJournalCheckPost = InventJournalCheckPost::newJournalCheckPost(JournalCheckPostType::Check, inventJournalTable);
    inventJournalCheckPost.parmThrowCheckFailed(true);

    try
    {

        inventJournalCheckPost.run();
    }
    catch
    {
        info("The journal contains errors.");
    }
}


And all the other classes for Journals go with the same base, so for example, for a Production Journal, the correct class to perform checking or posting is ProdJournalCheckPost. That is because all of them inherit from JournalCheckPost or JournalCheckPostLedger (which inherits from JournalCheckPost).


So that's it, that's how you post or check a journal with code.