How to create custom HTML control

To create 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 custom control to sort table records in a drag & drop style. D365 is using jQuery and we will use Sortable control as a UI base for our new custom control. The name of new custom control will be VKOSortableList.

Build class

Build class allows to define the properties that appear in the Visual Studio property sheet.

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

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

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

Build class for VKOSortableList will contain following properties:

  • Data Source. To state data source, which records will be sortable
  • Data Field Name. Field name, which content will be displayed in sortable blocks
  • Data Field Sort. Field name, which will contain integer value of record order.
  • SortableItems. Optional list of items to sort, may be used for test purposes or when we need to sort items, which are not in the table. It can be defined programmatically in run-timeclass or even on the form.

Build class for VKOSortableList control can be downloaded using this link.

Contract, which is used to describe sortable item. There we need two properties, one is "text" to show in UI and second one is recId to identify the record:

/// <summary>
/// The contract to store sortable item information.
/// </summary>
[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.

FormControlAttribute class attribute is used to define:

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

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

Common use of FormProperty:

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

Typical run-time class should contain following methods:

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

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

HTML template

It is entry point for web UI. Should contain HTML template and may contain any number of links to JavaScript or/and CSS resources if you need them.
Please be aware that there could be more than one instance of your control at same page. Control framework gracefully controls load of additional resources, which are stated in HTML template. They will be loaded only once. Also, it means that inline JavaScript will be executed only once. Bear that in mind and encapsulate all JavaScript logic required for custom control operation in object named same as 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 take a note that:

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

data-dyn-bind - handles many common DOM manipulations. There is short description of data-dyn-bind capabilities:

  • attr – applies values to 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

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

// As a best practice, wrap your code in function like this to protect the 'global namespace'.
// 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. VKOSortableList control will automatically appear in the list of controls:


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


VKOSortableTable is a simple table with two fields:


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


Troubleshooting

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

In Visual Studio, you can select 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 currently supported.



Please be aware that you can use console.log(anytype) to output values to web browser’s console and then investigate value. It can handle complex objects such as arrays/objects/DOMelements.

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

 

In Internet Explorer, you can do it by pressing F12

Search

About

DaxOnline.org is free platform that allows you to quickly store and reuse snippets, notes, articles related to Dynamics AX.

Authors are allowed to set their own AdSense units.
Join us.

Blog Tags