How to create custom HTML control

To create a custom HTML control in Dynamics 365 for Operations you will need to create:

  • Build class
  • Run-time class
  • HTML template resource
  • JS resource
  • CSS resource

As an example, we will be creating a custom control to sort table records in a drag and drop style. Since D365 uses jQuery,  we will use a sortable control as a UI base for our new custom control. Our new custom control will be named VKOSortableList.

Build class

First of all, we need to create a build class.  The build class allows us to define the properties that appear in the Visual Studio property sheet.

It allows us to create the following types of parameters, which will then be displayed by VS in form designer:

  • Binding properties – such as data source, data field, data method
  • Combo box with AOT artefacts filtered by AOT type, child classes or even field group member
  • List of parameters

This class will also be used as a contract class to fetch defined properties in run-time class.

The build class for VKOSortableList will contain the following properties:

  • Data Source – defines the data source and which records will be sortable
  • Data Field Name – states the field name and which content will be displayed in the sortable blocks
  • Data Field Sort – describes the field name containing an integer value of the record order.
  • SortableItems – Optional list of items to sort.  This may be used for test purposes or when we need to sort items which are not in the table. It can also be defined programmatically in run-time class or on the form.

You can download the build class for the VKOSortableList control using this link.

The contract, which is used to describe the sortable item, requires two properties.  One is “text” to show in the UI and the second one is recId to identify the record:

/// 
/// The contract to store sortable item information.
/// 
[DataContractAttribute]
class VKOSortableListItem
{
    str         text;
    ReFRecId    recId;
 
    [DataMemberAttribute("text"),
    FormDesignPropertyAttribute("text", "Data")]
    public str parmText(str _text = text)
    {
        if(!prmisDefault(_text))
        {
            text = _text;
        }
 
        return text;
    }
 
    [DataMemberAttribute("recId"),
    FormDesignPropertyAttribute("recId", "Data")]
    public RefRecId parmRecId(RefRecId _recId = recId)
    {
        if(!prmisDefault(_recId))
        {
            recId = _recId;
        }
 
        return recId;
    }
 
}

Run-time class

The run-time class defines server-side business logic.

The FormControlAttribute class attribute is used to define:

  • Template ID – refers to the “id” attribute of HTML element, which should be used as a template. The controlling JavaScript object should also have same name.
  • Resource bundle path – Path to the resource of type HTML under AOT->Resources
  • Build class name – name of build class, which was created above

The Control framework uses the FormProperty for the synchronization of property values between X++ and JavaScript.

Common uses of FormProperty:

  • Usually declared right below the class declaration
  • Instantiated in new method
  • Initialized in the applyBuild method
  • Read/written using parm* methods
  • FormBindingUtil::initbinding – used to bind the FormProperties to data fields and data methods on a data source

Typically, the run-time class should contain following methods:

  • new()
  • applyBuild()
  • parm* methods

The VKOSortableList run-time class contains additional methods to load the sortable item blocks from the data source and save the order of records. It can be downloaded using this link.

HTML template

This is the entry point for the web UI, so should contain the HTML template and may contain any number of links to JavaScript or/and CSS resources, if you require them.
Also, please be aware that there could be more than one instance of your control on the same page. The Control framework gracefully controls the loading of additional resources, which are stated in the HTML template, hence they will only be loaded once. Furthermore, the inline JavaScript will be executed only once, hence we should encapsulate all the JavaScript logic required for a custom control operation in an object named the same as the Template-ID.

HTML template for VKOSortableList control:

<ul id="VKOSortableList" data-dyn-bind="visible: $data.Visible, sizing: $dyn.layout.sizing($data), foreach: $data.SortableItems" >
    <li class="ui-state-default ui-sortable" data-dyn-bind="text: $data.text, attr: {recid: $data.recId}">Item 1</li>
</ul>

<script src="/resources/scripts/VKOSortableListControlJS.js"></script>
<link href="/resources/styles/VKOSortableListControlCSS.css" rel="stylesheet" type="text/css" />

Please note:

  • The HTML template element is a UL tag and it has an id attribute equal to VKOSortableList.
  • The data-dyn-bind attribute, which is a part of control framework, introduces new capabilities. In our example, for each SortableItems array element, it repeats the child LI tag.
  • We are loading additional JavaScript and CSS resources

data-dyn-bind – handles many common DOM manipulations. Below are some of the data-dyn-bind capabilities:

  • attr – applies values to the stated HTML attribute
    attr: {title: ‘Hello’, name: ‘Greeting’}
  • click – subscription to ‘click’ event
    click: $control.ElementClicked
  • css – adds/removes CSS class names
    css: {green: true, red: $control.red, yellow: $dyn.value($control.yellow)}
  • event – subscription to DOM event
    event: {mouseover: $data.elementHovered}
  • foreach – repeats the content of the child element
  • if – conditionally renders child element
  • sizing – specifies the height and width of the control, should be applied to root element
  • text – binds text content of the element to some value
  • visible – controls visibility of the element, should be applied to root element

In addition, more information can be found following next link

CSS

.ui-state-default {
	border: 1px solid #c5c5c5;
	background: #f6f6f6;
	font-weight: normal;
	color: #454545;
    text-decoration: none;
}
.ui-sortable {
    list-style-type: none;
    height: 25px;
    width: 100%;
    margin-top: 3px;
    padding: 4px;
}

JavaScript

In our case the JavaScript part is quite simple because the majority of UI interaction is handled by the jQuery Sortable control. We are simply initiating the Sortable control for a given element and subscribing to the control’s update event, which is triggered when the item block is dropped. We need it to grab the current order of blocks and pass this information to the server side to save new record order.

// As a best practice, wrap your code in function like this so the   'global namespace' is protected.
// Also as a best practice, use strict to catch common JS errors.
(function () {
    'use strict';
 
    $dyn.ui.defaults.VKOSortableList = {
        jqSortable: null
    };
 
    $dyn.controls.VKOSortableList = function (props) {
        var self = this;
 
        // Almost all controls 'extend' the Control base class.  This syntax is used to call the contructor for the base class.
        $dyn.ui.Control.apply(this, arguments);
 
        $dyn.ui.applyDefaults(this, props, $dyn.ui.defaults.VKOSortableList);
 
        var SortableItems = $dyn.peek(self.SortableItems);
        console.log(SortableItems);
 
        self.sortChange = function (event, ui) {
 
            console.log('------');
            var sequenceArr = new Array();
            $.each(this.jqSortable.find("li"), function (key, val) {
                console.log("item key: " + key + " - data: " + $(val).attr('recid') + " - text: " + $(val).text());
                sequenceArr.push($(val).attr('recid'));
            });
 
 
            if ($dyn.callFunction) {
                $dyn.callFunction(self.OrderModified, self, [sequenceArr.join()], function () { /* executes on success */ });
            }
        };
 
        self.applyContext = function (Func, Context) {
            return function () {
                return Func.apply(Context, arguments);
            }
        };
 
        self.jqSortable = $(props._element).sortable({
            update: self.applyContext(this.sortChange, this)
        });
    };
 
    $dyn.controls.VKOSortableList.prototype = $dyn.extendPrototype($dyn.ui.Control.prototype, {
    });
 
})();

Test form

To see our newly created control in action we need to create a test form. Firstly, the VKOSortableList control will automatically appear in the list of controls:

Custom control in the list of controls

We will add a new control of type VKOSortableList and select the following data source properties:

Custom control properties

VKOSortableTable is a simple table with two fields:

VKOSortableTable content

As a result, we will have the following list in the web browser, where we are able to sort records using a drag and drop style:

VKOSortableList control look

Troubleshooting

Modern web browsers (including Edge and Chrome) have powerful developer tools so they can give you plenty of information about the HTML structure, CSS tables acting on elements and the JavaScript console.

In Visual Studio, you may select the preferable web browser to use:

Dynamics 365 -> Options -> Default browser to use when running projects drop down,  will have a list of installed browsers. Google Chrome and Edge/Internet Explorer are currently supported.

VS default browser settings

Most of all, please be aware that you can use console.log(anytype) to output values to web browser’s console and then investigate the value. It can handle complex objects such as arrays/objects/DOM elements.

In Google Chrome, you can access the developer’s tools by pressing ctrl+shift+I (menu->More tools->Developer tools) and then clicking the console tab:

Chrome developer tools, console

Finally,  in Internet Explorer, you can do the same by pressing F12

Extensions are the future – time for a gatekeeper role

The message should have gotten through load and clear by now, customisation of Dynamics 365 for Operations (AX7) should, wherever possible, be done using Extensions. Your development team should have seen this being hammered home by Microsoft and the D365 community over the past months and especially since Microsoft told us that the Application Suite will be sealed by spring 2018. So how do you stop your development team from falling back on old habits by using their tried and test over layering techniques?

Continue reading

Running an Electronic Report from a menu item

Earlier I discussed how to create a new type of Electronic Report in Dynamics 365 for Operations (AX7), which can be viewed here,  Creating new Electronic Report. In this post, I want to look at how to modify the application so that a new Electronic Report can be run from a form using a menu item. Continue reading