Extensible control – HTML/JavaScript

The first part of this mini-tutorial showed how to create necessary classes for an extensible control. We have X++ classes for a control showing maps, we’ve added the control to a form and set a few properties. Now we have to add some HTML and JavaScript to do the job in browser.

Create a text file on disk, set its name to MyMap.htm and put this HTML code inside:

<div id="MyMap" data-dyn-bind="
    visible: $data.Visible,
    sizing: $dyn.layout.sizing($data)"></div>
 
<script src="https://www.google.com/jsapi"></script>
<script src="/resources/scripts/MyMap.js"></script>

It’s very simple. We have a single div representing our control, with appropriate ID. Then we use data-dyn-bind attribute to set some properties based on data from AX. It’s not strictly necessary for this example, but we would have to set some size anyway, so why not to use the right way?

At the bottom, we refer to some JavaScript files. The first one is the usual Google API, the other is a reference to a JavaScript file that we’ll add in a moment.

Create a new resource in your Visual Studio project, name it MyMapHTM and when asked for a file, use the file that you’ve just created.

We also need a resource for JavaScript. Create a file, MyMap.js, with this content:

(function () {
    'use strict';
    $dyn.controls.MyMap = function (data, element) {
        $dyn.ui.Control.apply(this, arguments);
        $dyn.ui.applyDefaults(this, data, $dyn.ui.defaults.MyMap);
    };
 
    $dyn.controls.MyMap.prototype = $dyn.ui.extendPrototype($dyn.ui.Control.prototype, {
        init: function (data, element) {
            var self = this;
 
            $dyn.ui.Control.prototype.init.apply(this, arguments);
 
            google.load("maps", 3, {
                callback: function () {
                    var map = new google.maps.Map(element, {
                            center: { lat: $dyn.value(data.Latitude), lng: $dyn.value(data.Longitude) },
                            zoom: $dyn.value(data.Zoom),
                            mapTypeId: google.maps.MapTypeId.HYBRID
                        });
                    }
            });
        }
    });
})();

If you have an API key, add it to the load() function in this way:

google.load("maps", 3, {
    other_params:"key=Abcdefg12345",
    callback: function () {

It all works for me without any API key, although it’s officially required. If needed, you can easily create a new API key. And you definitely should provide a key if you use this API in production.

I’m not going to explain the JavaScript code in detail. In short, it loads and calls the Google Maps API when our controls loads, it provides a callback function that sets properties of the map. It’s important to notice how we use values of our properties, such as $dyn.value(data.Latitude). These values come from the runtime class, MyMapControl.

As with HTML, create a resource, call it MyMapJS and add MyMap.js there.

Notice that you can edit resource files directly in Visual Studio.

editresource

And we’re done! Build the solution, run the form and you should see a map like this:

formwithmap

If you don’t see anything, make sure you’ve set Height and Width of the control, because my implementation doesn’t provide any minimal size.

If you have some other problem, you can use the debugger in Visual Studio (if it’s related to X++ classes), or press F12 in your browser to review DOM, debug JavaScript and so on.

Note that the control doesn’t merely show a picture; it’s a fully functional Google map – you can scroll, zoom the map or switch to StreetView, for example.

This control is deliberately very simple and doesn’t provide any additional logic, but you could easily utilize the JavaScript API to add pins or routes and do a plenty of other useful stuff. The control also doesn’t accept any input from users, so we didn’t need any binding to datasources nor any commands reacting to user actions, although all these things are possible. It’s always good to start with something simple and complicate things only when basics are clear enough.

Let me show just more one thing that the control already supports – setting properties from code.

Open the form in designer, find the map control (MyMapControl1) and set its AutoDeclaration property of to Yes. Then override form’s init() method and put the following code there:

public void init()
{
    super();
 
    MyMapControl1.parmLatitude(48.9745);
    MyMapControl1.parmLongitude(14.474);
    MyMapControl1.parmZoom(16);
}

When you run the form, you should see a map for coordinates provided in code and not those set in properties in designer. That was easy. 🙂

As you see, creating custom controls isn’t that complicated. The X++ classes we wrote have a few dozen lines of code, but they’re actually very simple; they do little more than just defining our three properties. If needed for your controls, you can put any arbitrary logic there and you’ll be able to manage and debug it as any other X++ code. How difficult is building the front end depends on your proficiency in HTML and especially JavaScript. You obviously can’t build a web UI without any knowledge of web technologies, but on the other hand, you build just a control and not the whole application.

When design robust controls, you should take into account several things not needed for simple examples like this, such as localization and responsive design.

Follow documentation (Build extensible controls) to learn more about development of extensible controls.

I think Microsoft made a smart move by designing this framework for extensible control and making it available to everybody. Although it was possible to create custom controls in older versions of AX as well (using ActiveX and managed controls), this is fully integrated (including design-time experience in Visual Studio) and it’s really easy to use. I don’t say it’s completely trivial, but it’s not anything obscure either. I’m looking forward to building more extensible controls myself.

16 Comments

    • You can put the .js file top any folder; it’s used only to create a resource from the file and then it can be deleted immediately after that. AX will use the resource, not an external file.

  1. Great example! I need to output many rows as through form control – it must work similar to FormGridControl. Is it possible to do it? Or each outputting row must be separate control with own binding?

    • Sure, it can be done. For example, the hierarchical grid has been built as an extensible control, so you can take a look at it.
      But no, I creating a control for each line doesn’t make a good sense to me. How would you add them to forms? How each of them would know which line to show. A grid is indeed a single logical control.

  2. Hi Martin,

    Thanks for this great article!
    I build my own custom control following the pattern and I add it to my form extension. Unfortunately, I had this error when I build the project .
    Error Path: [AxFormExtension/PurchReqTable.Extension/Design/Controls/Tab/TabPageDetails/Header/LinesPanel/LinesViewTab/LineViewHeaderDetailsTab]:Control ‘AxFormExtension/PurchReqTable.Extension/Design/Controls/Tab/TabPageDetails/Header/LinesPanel/LinesViewTab/LineViewHeaderDetailsTab’ has child MyCutomControl1′ which is not allowed at its current location by pattern ‘Fields and Field Groups’. ùdo you have any idea what causing this error.
    Thanks

    • That’s nothing wrong with your control; it means that “Field and Field Groups” subpattern accept fields and field groups, not other types of controls (including your custom control). If you need it there, you must change the pattern to “Custom”.

  3. Hi Martin,

    This example was super usefull, it save me a lot of research work.
    There is just one thing I want to accomplish, that’s refresh the control and thus the Google Map.
    I put multiple pins on the map but I want to be able to filter out certain pins based on a combobox on the same form.
    But I can’t get the control to reload, tried a bunch of methods on the control itself as wel as the element.redraw.

    Do you have any idea how we can achieve this?

    • Hi,

      Figured it out, just add an observable method on the parameter you expose via the build class.
      Then handle the change in your JScript code.

      • Hi Sven,

        Could you share the code or the principle you used when you did thist? I am trying to do the exact same thing that you did here.

        In any case, thanks!

      • Hi Sven,

        Could you please help understanding this? I am looking for the similar functionality please.

  4. This tutorial is perfect and useful. I can follow it and the result works well. Great work! Thank you.

  5. This is perfect! thx a lot. I use it to do my own with GeoJson and leaflet and works fine… except a bug about z-order display, all AX display (menu, error message, etc..) stay behind. any advice about it ?

    • Thanks a lot for this tutorial, but I have a problem when I’m trying to use leaflet in D365 to implement a Map. So please could you send me some example how D365 works with leaflet?
      I will show you my example if it is needed and if you want to review my code and tell me what is wrong and how can I manage it.
      Thanks a lot.

  6. Hi Martin Dráb,
    I need a help about this type of article. Is it possible to trigger some methods from javascript to x++ code?
    What I need:
    I have a map and I show coordinates on the map and it works fine. Now, i want to move the pin in the map and get back new values of the coordinates and to see them in x++ code.

    Thanks a lot.
    Irena

Comments are closed.