Extensible control – X++ classes – ww12345678 的部落格 | AX Helper
最新消息:ww12345678 的部落格重装上线,希望大家继续支持。

Extensible control – X++ classes

网络文摘 William 583浏览 0评论

User interface in AX 7 (Dynamics 365 for Operations) is now in web browser, which forced Microsoft to make many fundamental changes. Obviously, they had to rewrite all controls to HTML (plus JavaScript and CSS), but it has many additional consequences. For example, it’s not possible anymore to run X++ code on client; all X++ code runs on server. If something happens in browser, such as you click a button, the browser can still call X++ code on server, but the call over internet is quite slow. Another option is running client-side logic written in JavaScript. In either case, it’s quite different from how older versions of AX worked. On the other hand, it all gives us a plenty of new options.

AX developers usually don’t have to care about any HTML, JavaScript and so on; they deal with controls (such as buttons and grids), both in designer and in X++ code. The whole rendering to HTML, client scripting, communication with server and so on is done by controls themselves, with the help of AX kernel.

That you don’t usually have to care about how control work doesn’t mean that it isn’t useful. It can help you with debugging and exploring capabilities of existing controls, but most importantly it allows you to design new controls for your particular requirements. This is extremely powerful and it’s not too complicated either. Of course, you must know something about HTML and JavaScript, and you’ll find that designing a control that works great in all cases (small resolution, right-to-left languages etc.) isn’t completely trivial, but don’t bother about it right now.

Let’s build a simple control showing a map for given coordinates. I’m not going to dive into much details, explain all capabilities and so on; I want to show a case from beginning to end without any distractions. If you want to build a more useful control, you’ll still have to go to documentation and learn about additional features from there.

First of all, we need a so-called “build class”, which defines how the control behaves at design time in Visual Studio.

Create an X++ class called MyMapControlBuild and paste the following code there:

[FormDesignControlAttribute("My map")]
class MyMapControlBuild extends FormBuildControl
{
    real    latitude;
    real    longitude;
    int     zoom;
 
    [FormDesignPropertyAttribute("Latitude", "Map")]
    public real parmLatitude(real _latitude = latitude)
    {
        if (!prmIsDefault(_latitude))
        {
            latitude = _latitude;
        }
        return latitude;
    }
 
    [FormDesignPropertyAttribute("Longitude", "Map")]
    public real parmLongitude(real _longitude = longitude)
    {
        if (!prmIsDefault(_longitude))
        {
            longitude = _longitude;
        }
        return longitude;
    }
 
    [FormDesignPropertyAttribute("Zoom", "Map")]
    public int parmZoom(int _zoom = zoom)
    {
        if (!prmIsDefault(_zoom))
        {
            zoom = _zoom;
        }
        return zoom;
    }
}

The class inherits from FormBuildControl and has an attribute defining its human-friendly name. It also contains three fields (latitude, longitude and zoom) and corresponding parm* methods, each decorated with FormDesignPropertyAttribute. We’ll see these three properties in the form designer; we’ll be able to set their values and later we’ll use them to show something on the map.

The second argument of FormDesignPropertyAttribute is the category of properties, which seems to be ignored in the moment (but I still think you should use it; hopefully it will become supported later).

Then we need one more class, a so-called “runtime class”. It represents the X++ part of the actual control when rendered in user interface. You could put more logic there, but in this case, we’ll only expose our properties.

Let’s do it in a few steps. Create a new class, MyMapControl, with the following code.

[FormControlAttribute('MyMap', '', classstr(MyMapControlBuild))]
class MyMapControl extends FormTemplateControl
{
    public void new(FormBuildControl _build, FormRun _formRun)
    {
        super(_build, _formRun);
 
        this.setTemplateId('MyMap');
        this.setResourceBundleName('/resources/html/MyMap');
    }
}

Create three FormProperty variables.

FormProperty latitude;
FormProperty longitude;
FormProperty zoom;

For each property, add a parm* method decorated with FormPropertyAttribute. These properties will be available in JavaScript in browser.

[FormPropertyAttribute(FormPropertyKind::Value, "Latitude")]
public real parmLatitude(real _value = latitude.parmValue())
{
    if (!prmIsDefault(_value))
    {
        latitude.setValueOrBinding(_value);
    }
 
    return latitude.parmValue();
}
 
[FormPropertyAttribute(FormPropertyKind::Value, "Longitude")]
public real parmLongitude(real _value = longitude.parmValue())
{
    if (!prmIsDefault(_value))
    {
        longitude.setValueOrBinding(_value);
    }
 
    return longitude.parmValue();
}
 
[FormPropertyAttribute(FormPropertyKind::Value, "Zoom")]
public int parmZoom(int _value = zoom.parmValue())
{
    if (!prmIsDefault(_value))
    {
        zoom.setValueOrBinding(_value);
    }
 
    return zoom.parmValue();
}

We also need to initialize FormProperty objects and associate them with parm* methods. Put this code to the constructor, below super().

latitude = properties.addProperty(methodStr(MyMapControl, parmLatitude), Types::Real);
longitude = properties.addProperty(methodStr(MyMapControl, parmLongitude), Types::Real);
zoom = properties.addProperty(methodStr(MyMapControl, parmZoom), Types::Integer);

The last missing piece is the initialization of the control from the build class. We take values of designer properties and put them into our actual control.

public void applyBuild()
{
    super();
 
    MyMapControlBuild build = this.build();
 
    if (build)
    {
        this.parmLatitude(build.parmLatitude());
        this.parmLongitude(build.parmLongitude());
        this.parmZoom(build.parmZoom());
    }
 
}

Here is the complete class, so you can easily copy and paste the code.

[FormControlAttribute('MyMap', '', classstr(MyMapControlBuild))]
class MyMapControl extends FormTemplateControl
{
    FormProperty latitude;
    FormProperty longitude;
    FormProperty zoom;
 
    public void new(FormBuildControl _build, FormRun _formRun)
    {
        super(_build, _formRun);
 
        this.setTemplateId('MyMap');
        this.setResourceBundleName('/resources/html/MyMap');
 
        latitude = properties.addProperty(methodStr(MyMapControl, parmLatitude), Types::Real);
        longitude = properties.addProperty(methodStr(MyMapControl, parmLongitude), Types::Real);
        zoom = properties.addProperty(methodStr(MyMapControl, parmZoom), Types::Integer);
    }
 
    public void applyBuild()
    {
        super();
 
        MyMapControlBuild build = this.build();
 
        if (build)
        {
            this.parmLatitude(build.parmLatitude());
            this.parmLongitude(build.parmLongitude());
            this.parmZoom(build.parmZoom());
        }
    }
 
    [FormPropertyAttribute(FormPropertyKind::Value, "Latitude")]
    public real parmLatitude(real _value = latitude.parmValue())
    {
        if (!prmIsDefault(_value))
        {
            latitude.setValueOrBinding(_value);
        }
 
        return latitude.parmValue();
    }
 
    [FormPropertyAttribute(FormPropertyKind::Value, "Longitude")]
    public real parmLongitude(real _value = longitude.parmValue())
    {
        if (!prmIsDefault(_value))
        {
            longitude.setValueOrBinding(_value);
        }
 
        return longitude.parmValue();
    }
 
    [FormPropertyAttribute(FormPropertyKind::Value, "Zoom")]
    public int parmZoom(int _value = zoom.parmValue())
    {
        if (!prmIsDefault(_value))
        {
            zoom.setValueOrBinding(_value);
        }
 
        return zoom.parmValue();
    }
}

Build the solution, create a new form and add the new control, My map (the name comes from FormDesignControlAttribute).

Control selection

We can’t successfully run the form yet, because we still haven’t defined how the control should render, but we can work with the control in designer, set its properties, possibly override its methods and so on.

designer

If you open properties, you’ll see our three custom properties (Latitude, Longitude and Zoom), together with many other properties common to all controls (such as Visible). I would expect to see a new group of properties, Map, but it’s not how it works in the moment.

Fill in your favorite GPS coordinates, a zoom level and the required size of the control.

properties

This is all for now, we’ll add HTML and JavaScript in the next blog post.

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址