Thursday, September 28, 2017

Dynamics 365 for Finance and Operations Extensible Controls - Server Side to Client Side and Back

Reference Link : http://www.bootes.co/EN/Articles/Dynamics-365-for-Finance-and-Operations-Extensible-Controls-Server-Side-to-Client-Side-and-Back.aspx

Below details from above source

In this article we're going to show you how a simple extensible control that's controlled by a data source and a grid control on a form works. The user will select a record on the form and the associated picture will be displayed in the extensible control.
If you do not have any previous experience with Dynamics 365 for Finance and Operations Extensible Controls it is highly recommended that you go through this tutorial:
It will explain the basic concepts surrounding extensible control development in Dynamics 365 for Finance and Operations.
Getting Started.
1. Download the project file for the control:
2. Unzip the file in your development Virtual Machine.
3. Import the project file into visual studio, in a new solution:

4. Build the solution.
5. Synchronize the database.
6. Run the project by pressing F5 or Debug -> Start Debugging.
7. In the Action pane at the top of the page click on the "Create Data" button.
8. Jump from record to record and see how the image on the right is updated accordingly.
How it Works.
    [DataSource]
    class DBImageXCtrlTable
    {
        /// <summary>
        ///
        /// </summary>
        /// <returns></returns>
        public int active()
        {
            int ret;
            ret = super();
            DBImageXCtrl oDBImageXCtrl1 = DBImageXCtrl1;
            oDBImageXCtrl1.Key(DBImageXCtrlTable.iKey);
            return ret;
        }
    }
When you're switching records in the grid the "active" method for the "DBImageXCtrlTable" data source is being called. The "active" method is calling a property getter/setter called Key in the X++ runtime class.
    [FormPropertyAttribute(FormPropertyKind::Value, "Key")]
    public int Key(int _iValue = m_iKey.parmValue())
    {
        m_iKey.setValueOrBinding(_iValue);
        return m_iKey.parmValue();
    }
This Key getter/setter in the X++ runtime class (the "DBImageXCtrl" class) has a FormPropertyAttribute and is participating in the observable pattern in JavaScript on the client.
        DBIC.YieldCallFunction = function (_iKey) {
            var oParams = { _iKey: _iKey };
            $dyn.callFunction(self.GetImage, self, oParams, DBIC.ChangeImage);
        }
        $dyn.observe(this.Key, function (_iKey) {
            window.setTimeout(DBIC.YieldCallFunction, 0, _iKey);
            //You have to yield the code. If you eliminate the line above
            //and uncomment the following two will, the control will not work.
            //var oParams = { _iKey: _iKey };
            //$dyn.callFunction(self.GetImage, self, oParams, DBIC.ChangeImage);
        });
Every time the Key property value changes the code above gets triggered in JavaScript (the $dyn.observe) . This code that is getting triggered is calling a method that is in the X++ runtime class called GetImage and receiving the results from GetImage in a function called DBIC.ChangeImage. But it is not calling the method directly, it is calling the method asynchronously by using window.setTimeout in a process known as code yielding. If it were to call the method directly the first image would be loaded but in subsequent "activate" calls you would get this error in the console of your browser:
And the image would not change. You can try uncommenting the commented lines and removing the first line in the method and you'll notice the effect.
    [FormCommandAttribute("GetImage")]
    public str GetImage(int _iKey)
    {
        DBImageXCtrlTable oDBImageXCtrlTable;
        container oImage;
        str sURL;
        select * from oDBImageXCtrlTable where oDBImageXCtrlTable.iKey == _iKey;
        DBImageXCtrlDataContract oDBImageXCtrlDataContract = new DBImageXCtrlDataContract();
        if (oDBImageXCtrlTable)
        {
            oImage = oDBImageXCtrlTable.cImage;
            oDBImageXCtrlDataContract.URL(conPeek(oImage, 3));
        }
        str sReturn;
        sReturn = FormJsonSerializer::serializeClass(oDBImageXCtrlDataContract);
        return sReturn;
    }
The get image method reads the URL of the image from the database and serializes it JSON and returns it to this JavaScript function (which was specified as the call back function in the $dyn.callFunction's last parameter):
        DBIC.ChangeImage = function (_sJSON)
        {
            var oObject = $.parseJSON(_sJSON, true);
            $(element).empty();
            $(element).append("<img src='" + oObject.URL + "' />");
        }
This JavaScript empties the main div for the control and creates a new image object with the new URL.
Instead of all this back and forth between X++ (server-side)/Javascript (client-side), why not just simply have a string URL property in the X++ runtime class? You can do it too, but because there is a limit on the size of observable properties URLs might get truncated. There's also a limit on the size of the return of a method but it's much higher.
Apendix A – Creating the demo files manually
Create a new model with the following specifications:
Name:DBImageXControl
Layer:ISV
Package:Create in New Package
References:ApplicationPlatform, ApplicationFoundation
Create a new Dynamics 365 Unified Operations project called DBImageXControl inside the model you've created previously.
1. Create an X++ runtime class called "DBImageXCtrl" with the following code:
[FormControlAttribute("DBImageXCtrl","",classstr(DBImageXCtrlBuild))]
class DBImageXCtrl extends FormTemplateContainerControl
{
    private FormProperty m_iKey;
    protected void new(FormBuildControl _build, FormRun _formRun)
    {
        super(_build, _formRun);
        this.setResourceBundleName("/resources/html/DBImageXCtrl");
        m_iKey = this.addProperty(methodstr(DBImageXCtrl, Key), Types::Integer);
    }
    [FormPropertyAttribute(FormPropertyKind::Value, "Key")]
    public int Key(int _iValue = m_iKey.parmValue())
    {
        m_iKey.setValueOrBinding(_iValue);
        return m_iKey.parmValue();
    }
    [FormCommandAttribute("GetImage")]
    public str GetImage(int _iKey)
    {
        DBImageXCtrlTable oDBImageXCtrlTable;
        container oImage;
        str sURL;
        select * from oDBImageXCtrlTable where oDBImageXCtrlTable.iKey == _iKey;
        DBImageXCtrlDataContract oDBImageXCtrlDataContract = new DBImageXCtrlDataContract();
        if (oDBImageXCtrlTable)
        {
            oImage = oDBImageXCtrlTable.cImage;
            oDBImageXCtrlDataContract.URL(conPeek(oImage, 3));
        }
        str sReturn;
        sReturn = FormJsonSerializer::serializeClass(oDBImageXCtrlDataContract);
        return sReturn;
    }
}
2. Create an X++ build class called "DBImageXCtrlBuild" with the following code:
[FormDesignControlAttribute("DBImageXCtrl")]
class DBImageXCtrlBuild extends FormBuildContainerControl
{
}
3. Create a class called "DBImageXCtrlDataContract" with the following code:
[DataContractAttribute]
class DBImageXCtrlDataContract extends FormDataContract
{
    FormProperty m_sURL;
    public void new()
    {
        super();
        m_sURL = this.properties().addProperty(methodStr(DBImageXCtrlDataContract, URL), Types::String);
    }
    [DataMemberAttribute("URL")]
    public str URL(str _value = m_sURL.parmValue())
    {
        if(!prmisDefault(_value))
        {
            m_sURL.parmValue(_value);
        }
        return _value;
    }
}
4. Create an HTM resource (DBImageXCtrlHTM – DBImageXCtrl.htm) with this markup:
<script src="/resources/scripts/DBImageXCtrl.js"></script>
<div id="DBImageXCtrl" data-dyn-bind="sizing: { height: $data.Height, width: $data.Width }, visible: $data.Visible">
</div>

5. Create a JS resource (DBImageXCtrlJS – DBImageXCtrl.js) with this code:
DBIC = {};
(function () {
    'use strict';
    $dyn.ui.defaults.DBImageXCtrl = {};
    $dyn.controls.DBImageXCtrl = function (data, element) {
        var self = this;
        $dyn.ui.Control.apply(self, arguments);
        $dyn.ui.applyDefaults(self, data, $dyn.ui.defaults.DBImageXCtrl);
        DBIC.ChangeImage = function (_sJSON)
        {
            var oObject = $.parseJSON(_sJSON, true);
            $(element).empty();
            $(element).append("<img src='" + oObject.URL + "' />");
        }
        DBIC.YieldCallFunction = function (_iKey) {
            var oParams = { _iKey: _iKey };
            $dyn.callFunction(self.GetImage, self, oParams, DBIC.ChangeImage);
        }
        $dyn.observe(this.Key, function (_iKey) {
            window.setTimeout(DBIC.YieldCallFunction, 0, _iKey);
            //You have to yield the code. If you eliminate the line above
            //and uncomment the following two will, the control will not work.
            //var oParams = { _iKey: _iKey };
            //$dyn.callFunction(self.GetImage, self, oParams, DBIC.ChangeImage);
        });
    }
    $dyn.controls.DBImageXCtrl.prototype = $dyn.extendPrototype($dyn.ui.Control.prototype, {});
})();

Create the Table that Populates the Grid.
To populate the grid, create a table called "DBImageXCtrlTable". This table will have three fields: iKey an Integer field, sDescription a String field (size 100) and cImage a container field. The Key will be the iKey field. The table will have this structure:
Create the Form with the Grid and the Control
Create a form called "DBImageXForm" with the following structure:
The form will have:
A data source called "DBImageXCtrlTable" for the "DBImageXCtrlTable" table. This data source will have this code in the overridden "Active" method:
    [DataSource]
    class DBImageXCtrlTable
    {
        /// <summary>
        ///
        /// </summary>
        /// <returns></returns>
        public int active()
        {
            int ret;
            ret = super();
            print "active key:", DBImageXCtrlTable.iKey;
            DBImageXCtrl oDBImageXCtrl1 = DBImageXCtrl1;
            oDBImageXCtrl1.Key(DBImageXCtrlTable.iKey);
            return ret;
        }
    }
A "Create Data" button in an Action Pane with the overridden "click" method:
    [Control("Button")]
    class CreateData
    {
        /// <summary>
        ///
        /// </summary>
        public void clicked()
        {
            DBImageXCtrlTable oDBImageXCtrlTable;
            ImageReference oImageReference;
            delete_from oDBImageXCtrlTable;
            ttsbegin;
            oImageReference = ImageReference::constructForUrl("https://bootes.blob.core.windows.net/dbimagexctrl/Audi_A8_L_800.jpg");
            oDBImageXCtrlTable.iKey = 1;
            oDBImageXCtrlTable.sDescription = "Audi A8 L";
            oDBImageXCtrlTable.cImage = oImageReference.pack();
            oDBImageXCtrlTable.insert();
            oImageReference = ImageReference::constructForUrl("https://bootes.blob.core.windows.net/dbimagexctrl/Audi_RS3_Sportback_800.jpg");
            oDBImageXCtrlTable.iKey = 2;
            oDBImageXCtrlTable.sDescription = "Audi RS3 Sportback";
            oDBImageXCtrlTable.cImage = oImageReference.pack();
            oDBImageXCtrlTable.insert();
            oImageReference = ImageReference::constructForUrl("https://bootes.blob.core.windows.net/dbimagexctrl/Audi_RS4_Avant_800.jpg");
            oDBImageXCtrlTable.iKey = 3;
            oDBImageXCtrlTable.sDescription = "Audi RS4 Avant";
            oDBImageXCtrlTable.cImage = oImageReference.pack();
            oDBImageXCtrlTable.insert();
            oImageReference = ImageReference::constructForUrl("https://bootes.blob.core.windows.net/dbimagexctrl/Audi_RS5_Coupe_800.jpg");
            oDBImageXCtrlTable.iKey = 4;
            oDBImageXCtrlTable.sDescription = "Audi RS5 Coupe";
            oDBImageXCtrlTable.cImage = oImageReference.pack();
            oDBImageXCtrlTable.insert();
            oImageReference = ImageReference::constructForUrl("https://bootes.blob.core.windows.net/dbimagexctrl/Audi_SQ5_TFSI_800.jpg");
            oDBImageXCtrlTable.iKey = 5;
            oDBImageXCtrlTable.sDescription = "Audi SQ5 TFSI";
            oDBImageXCtrlTable.cImage = oImageReference.pack();
            oDBImageXCtrlTable.insert();
            ttscommit;
            DBImageXCtrlTable_ds.research();
            super();
        }
    }
A group control with 2 columns:
A child group control with the grid:

Another child group with the DBImageXCtrl control:


Extensible Controls in Dynamics 365 for Finance and Operations - Tutorial

Reference Link: http://www.bootes.co/EN/Articles/Extensible-Controls-in-Dynamics-365-for-Finance-and-Operations-Tutorial.aspx

Below details from above source

n Dynamics 365 for Finance and Operations (D365) Extensible Controls take on the role of the Managed Host Control of AX 2012. In AX 2012 the Managed Host Control could either be a Windows Forms Control or a WPF control. In its simplest form in D365 an Extensible Control is a piece of HTML with JavaScript functionality.
Although at first it might seem that Extensible Controls cannot carry out many of the tasks of Managed Host Controls and that Extensible Controls are limited, let me assure you that it isn't the case. From a UX perspective, HTML5 coupled with JavaScript can do anything that a WF or WPF control can do. It can even outperform them. Given the fact that HTML5 and JavaScript are present in so many devices, and in so many different platforms, it is beyond doubt the user interface language of choice by today's standards.
Now having said that, developing Extensible Controls is much more complicated than writing WF or WPF controls. In this tutorial, we will try to demystify that complexity. To understand this tutorial you must have a basic knowledge of HTML, CSS, JavaScript, JQuery and X++.
We are going to build an interactive tile navigation menu that you can include in any form. It will have some tiles that when clicked will take the user to another form. The tiles will have two sizes and the user will be able to change the background color of the control. This tutorial does not include data binding and will not involve the build class.
At the end of this tutorial you will know:
  • How to trigger an action in JavaScript from X++. A user presses a button on the form and the control displays a message box.
  • How to use properties and the observable pattern. A user selects a color from a drop-down and changes the background color for the control.
  • How to use a method to load data into the control using JSON. Tile information such as position, size, caption and display menu item are loaded from X++ during run time.
  • How to use a method to trigger an action in X++ that originates in JavaScript. A user clicks on a tile in the control and gets redirected to the corresponding D365 for Finance and Operations page via the associated Display Menu Item for the tile.
Control Physical Structure
An Extensible Control is made up of five files:
  • An X++ build class. Which houses the control's design time properties menu. We won't go into this class in this tutorial.
  • An X++ runtime class. Which contains properties and methods which we can call from JavaScript or from X++.
  • An HTML resource file.
  • A JavaScript resource file.
  • A CSS resource file. Optional.
Create the Basic Extensible Control Project Structure
1. Create a new model called NavMenu in a new package and make it reference Application Foundation and Application Platform.



2. Create a new operations project. Call this new project "NavigationMenu". 
3. Change the project's model to NavMenu. Right click on the project in solution explorer and click properties.
4. Right click on your computer's desktop then "New -> Text Document" three times.
5. Rename the three files to:
NAMNavigationMenuCtrl.htm
NAMNavigationMenuCtrl.js
NAMNavigationMenuCtrl.css
6. Create a resource called NAMNavigationMenuCtrlHTM and select the NAMNavigationMenuCtrl.htm file from the desktop into it.
7. Create a resource called NAMNavigationMenuCtrlJS and select the NAMNavigationMenuCtrl.js file from the desktop into it.
8. Create a resource called NAMNavigationMenuCtrlCSS and select the NAMNavigationMenuCtrl.css file from the desktop into it.
9. Create a class called NAMNavigationMenuCtrl.
10. Create a class called NAMNavigationMenuCtrlBuild.
11. You should now have the following project structure:
12. In the NAMNavigationMenuCtrl class include the following code:
[FormControlAttribute("NAMNavigationMenuCtrl","",classstr(NAMNavigationMenuCtrlBuild))]
class NAMNavigationMenuCtrl extends FormTemplateContainerControl
{
    protected void new(FormBuildControl _build, FormRun _formRun)
    {
        super(_build, _formRun);
        this.setResourceBundleName("/resources/html/NAMNavigationMenuCtrl");
    }
}
13. In the NAMNavigationMenuCtrlBuild class include the following code:
[FormDesignControlAttribute("NAMNavigationMenuCtrl")]
class NAMNavigationMenuCtrlBuild extends FormBuildContainerControl
{
}
14. In the NAMNavigationMenuCtrl.htm file include the following markup:
<script src="/resources/scripts/NAMNavigationMenuCtrl.js"></script>
<link href="/resources/styles/NAMNavigationMenuCtrl.css" rel="stylesheet" type="text/css" />
<div id="NAMNavigationMenuCtrl" data-dyn-bind="sizing: { height: $data.Height, width: $data.Width }, visible: $data.Visible">
    <p>This is the NAMNavigationMenuCtrl</p>
</div>
15. In the NAMNavigationMenuCtrl.js file include the following code:
(function () {
    'use strict';
    $dyn.ui.defaults.NAMNavigationMenuCtrl = {};
    $dyn.controls.NAMNavigationMenuCtrl = function (data, element) {
        var self = this;
        $dyn.ui.Control.apply(self, arguments);
        $dyn.ui.applyDefaults(self, data, $dyn.ui.defaults.NAMNavigationMenuCtrl);
    }
    $dyn.controls.NAMNavigationMenuCtrl.prototype = $dyn.extendPrototype($dyn.ui.Control.prototype, {});
})();
16. We will add styles to the NAMNavigationMenuCtrl.css file later.
17. Build the solution.
18. Create a model called NavMenuTest in a new package and make it reference the Application Platform, Application Suite and NavMenu models.
Create new model Dynamics 365 for Finance and Operations Step 1

Create new model Dynamics 365 for Finance and Operations Step 2


Create new model Dynamics 365 for Finance and Operations Step 3


Create new model Dynamics 365 for Finance and Operations Step 4

Create new model Dynamics 365 for Finance and Operations Step 5
19. Create a new project in the same solution called NavigationMenuTest, right click on this project select properties and change the model to NavMenuTest.
20. Add a new form to the NavigationMenuTest project and call it NATForm1.
21. Make NavigationMenuTest the startup project and NATForm1 the startup object.


22. Open the NATForm1 form, right click on design and select the NAMNavigationMenuCtrl.
23. Start debugging. You should see the caption of the Form "NATForm1" with the control underneath "This is the NAMNavigationMenuCtrl". The caption for the control comes from the htm file.
And this is all it takes to have a simple Dynamics 365 for Finance and Operations Extensible Control "hello world". Now we're going to look at the element object in JavaScript.
The element object gets passed as a parameter in the main function in JavaScript:
    $dyn.controls.NAMNavigationMenuCtrl = function (data, element) {
        …
    }
The element object represents the main div of the control in HTML:
<div id="NAMNavigationMenuCtrl" data-dyn-bind="sizing: { height: $data.Height, width: $data.Width }, visible: $data.Visible">
    …
</div>
So we can use JQuery to modify the element object and in consequence the main div. We will include the following line of code into our JavaScript:
$(element).css("background-color", "yellow");
Complete .js code:
(function () {
    'use strict';
    $dyn.ui.defaults.NAMNavigationMenuCtrl = {};
    $dyn.controls.NAMNavigationMenuCtrl = function (data, element) {
        var self = this;
        $dyn.ui.Control.apply(self, arguments);
        $dyn.ui.applyDefaults(self, data, $dyn.ui.defaults.NAMNavigationMenuCtrl);
        $(element).css("background-color", "yellow");
    }
    $dyn.controls.NAMNavigationMenuCtrl.prototype = $dyn.extendPrototype($dyn.ui.Control.prototype, {});
})();
Press F5 or Debug -> Start Debugging and you will notice that the div's background will be changed to yellow:
In the div we have the data-dyn-bind attribute:
<div … data-dyn-bind="sizing: { height: $data.Height, width: $data.Width }, visible: $data.Visible">
This binding handler attribute maps to the property sheet of the extensible control and the height, width and visible properties:
To test this, we're going to set the height and width properties manually:
We're setting it to 1500x500 pixels, when we run the form again, this is the result:
X++ Interaction
The JavaScript inside the control can receive instructions from X++. The JavaScript can also send instructions to X++ and in this operation, receive JSON data from X++. It's a two-way street.
To demonstrate sending commands from X++ to JavaScript we're going to add code to the X++ runtime class (NAMNavigationMenuCtrl) and to JavaScript. The objective is that when a user clicks a button in the NATForm1 form a message box is displayed by the control.
1. Add this class level variable to the NAMNavigationMenuCtrl class:
private FormProperty m_dtSendCommandToJavaScript;
2. In the NAMNavigationMenuCtrl class constructor initialize the variable:
m_dtSendCommandToJavaScript = this.addProperty(methodstr(NAMNavigationMenuCtrl, SendCommandToJavaScript), Types::UtcDateTime);
3. Add this getter/setter to the NAMNavigationMenuCtrl:
    [FormPropertyAttribute(FormPropertyKind::Value, "SendCommandToJavaScript")]
    public utcdatetime SendCommandToJavaScript(utcdatetime _dtValue = m_dtSendCommandToJavaScript.parmValue())
    {
        _dtValue = DateTimeUtil::getSystemDateTime();
        m_dtSendCommandToJavaScript.setValueOrBinding(_dtValue);
        return m_dtSendCommandToJavaScript.parmValue();
    }
4. The NAMNavigationMenuCtrl class should now look like this:
[FormControlAttribute("NAMNavigationMenuCtrl","",classstr(NAMNavigationMenuCtrlBuild))]
class NAMNavigationMenuCtrl extends FormTemplateContainerControl
{
    private FormProperty m_dtSendCommandToJavaScript;
    protected void new(FormBuildControl _build, FormRun _formRun)
    {
        super(_build, _formRun);
        this.setResourceBundleName("/resources/html/NAMNavigationMenuCtrl");
        m_dtSendCommandToJavaScript = this.addProperty(methodstr(NAMNavigationMenuCtrl, SendCommandToJavaScript), Types::UtcDateTime);
    }
    [FormPropertyAttribute(FormPropertyKind::Value, "SendCommandToJavaScript")]
    public utcdatetime SendCommandToJavaScript(utcdatetime _dtValue = m_dtSendCommandToJavaScript.parmValue())
    {
        _dtValue = DateTimeUtil::getSystemDateTime();
        m_dtSendCommandToJavaScript.setValueOrBinding(_dtValue);
        return m_dtSendCommandToJavaScript.parmValue();
    }
}
5. In the JavaScript resource file add this code:
        var m_bSendCommandToJavaScript = false;
        $dyn.observe(this.SendCommandToJavaScript, function (_dtParam) {
            if (m_bSendCommandToJavaScript == true)
            {
                alert("SendCommandToJavaScript Invoked");
            }
            m_bSendCommandToJavaScript = true;         
        });
6. So that the JavaScript resource file will end up looking like this:
(function () {
    'use strict';
    $dyn.ui.defaults.NAMNavigationMenuCtrl = {};
    $dyn.controls.NAMNavigationMenuCtrl = function (data, element) {
        var self = this;
        $dyn.ui.Control.apply(self, arguments);
        $dyn.ui.applyDefaults(self, data, $dyn.ui.defaults.NAMNavigationMenuCtrl);
        $(element).css("background-color", "yellow");
        var m_bSendCommandToJavaScript = false;
        $dyn.observe(this.SendCommandToJavaScript, function (_dtParam) {
            if (m_bSendCommandToJavaScript == true)
            {
                alert("SendCommandToJavaScript Invoked");
            }
            m_bSendCommandToJavaScript = true;         
        });
    }
    $dyn.controls.NAMNavigationMenuCtrl.prototype = $dyn.extendPrototype($dyn.ui.Control.prototype, {});
})();
7. In the NATForm1 form set the NAMNavigationMenuCtrl1 to Auto Declaration = yes.
8. Create a button control in NATForm1 and set the text to "Click Me".
9. Override the clicked event and add this line of code:
NAMNavigationMenuCtrl1.SendCommandToJavaScript();
10. The NATForm1 class should now look like this:
[Form]
public class NATForm1 extends FormRun
{
    [Control("Button")]
    class FormButtonControl1
    {
        /// <summary>
        ///
        /// </summary>
        public void clicked()
        {
            NAMNavigationMenuCtrl1.SendCommandToJavaScript();
            super();
        }
    }
}
11. Press F5 to start debugging (or Debug -> Start Debugging). Every time you click on the "Click Me" button you will get a "SendCommandToJavaScript Invoked" message box.
In JavaScript the this.SendCommandToJavaScript variable is created because the "SendCommandToJavaScript" getter/setter in the X++ runtime class is exposed (public). The naming of the variable in JavaScript depends on the FormPropertyAttribute's second parameter. If you were to change the getter/setter attribute to this:
[FormPropertyAttribute(FormPropertyKind::Value, "SendCommandToJavaScriptAAAA")]
You would have to change the variable name in JavaScript to this.SendCommandToJavaScriptAAAA.
The framework's Interaction Service automatically synchronizes the JavaScript variables with the X++ runtime class properties with no intervention whatsoever.
The Extensible Control framework also follows what is called an observable pattern and that's what this JavaScript code is all about:
        $dyn.observe(this.SendCommandToJavaScript, function (_dtParam) {
        …
        });
JavaScript reacts to the changes of the value of the this.SendCommandToJavaScript variable and executes this code:
            if (m_bSendCommandToJavaScript == true)
            {
                alert("SendCommandToJavaScript Invoked");
            }
            m_bSendCommandToJavaScript = true;
Only when the this.SendCommandToJavaScript value changes. So now you're probably wondering what's the purpose of the m_bSendCommandToJavaScript variable. Its purpose is to prevent the code from firing when the control is being initialized. The value of this.SendCommandToJavaScript variable will inevitably change from nothing to something when being initialized. We only need to display the message box when the user clicks the button and not when the form is loading. You can try removing the variable and observing the effect.
And that finally takes us to the getter/setter and why we're using utcdatetime.
    public utcdatetime SendCommandToJavaScript(utcdatetime _dtValue = m_dtSendCommandToJavaScript.parmValue())
    {
        _dtValue = DateTimeUtil::getSystemDateTime();
        m_dtSendCommandToJavaScript.setValueOrBinding(_dtValue);
        return m_dtSendCommandToJavaScript.parmValue();
    }
Every time this getter/setter is called the system date/time will inevitably change and the observable in JavaScript will be triggered.
This is not the intended use of observable properties. But they are useful when we want to trigger an event from X++. We will now go into the original intended usage of observable properties.
In NATForm1 we want to have a FormMenuButtonControl to allow the user to change the background color of the navigation menu control.
1. If you want to avoid having unnecessary code erase the FormProperty, the FormProperty initialization and the getter/setter from the X++ runtime class from the previous demonstration. Also erase the observable function from JavaScript and the "Click Me" button in the form.
2. Erase the following line from the htm resource file:
<p>This is the NAMNavigationMenuCtrl</p>
3. Erase this line in the JavaScript resource:
$(element).css("background-color", "yellow");
4. And replace it with this:
$(element).css("border", "1px solid black");
$(element).css("position", "relative");
5. Create this class level variable in the X++ runtime class:
private FormProperty m_iBackgroundColor;
6. Initialize the FormProperty variable in the X++ runtime class constructor:
m_iBackgroundColor = this.addProperty(methodstr(NAMNavigationMenuCtrl, BackgroundColor), Types::Integer);
7. Add this getter/setter method to the X++ runtime class:
    [FormPropertyAttribute(FormPropertyKind::Value, "BackgroundColor")]
    public int BackgroundColor(int _iValue = m_iBackgroundColor.parmValue())
    {
        m_iBackgroundColor.setValueOrBinding(_iValue);
        return m_iBackgroundColor.parmValue();
    }
8. Add this function to the JavaScript resource:
        $dyn.observe(this.BackgroundColor, function (_iParam) {
            switch (_iParam)
            {
                case 0: //Gray
                    $(element).css("background-color", "#EAEAEA");
                    break;
                case 1: //Light blue
                    $(element).css("background-color", "#C7E0F4");
                    break;
                case 2: //Blue
                    $(element).css("background-color", "#002050");
                    break;
                case 3: //Black
                    $(element).css("background-color", "#000000");
                    break;
            }
        });
9. Open NATForm1, create an Action Pane Control, a Button Group, a Menu Button and four Button controls:
10. For the Menu Button control set the text property to "Background color".
11. For the Button controls set the Text property to "Gray", "Light blue", "Blue" and "Black" beginning from the top and going downwards.
12. Override the clicked event for the four button controls and include the following code, beginning at 0 for grey and ending at 3 for black (1 for light blue and 2 for blue):
        public void clicked()
        {
            NAMNavigationMenuCtrl1.BackgroundColor(0);
            super();
        }
13. Press F5 or Debug -> Start Debugging and using the "Background color" menu change the background color of the control.
Loading Data into The Control Using JSON
Properties can be used to pass scalar variables of data between X++ and the control. But what if you need an array or a matrix (table)? In the Navigation Menu Control the requirement is that for each tile the X & Y position, the size, the caption, the display menu item be stored in a table and then passed at runtime. For the purposes of this tutorial we won't actually use a table, but we will pretend that the data came from a table.
To pass information from JavaScript to X++ and back Extensible Controls use JSON. JSON is short for JavaScript Object Notation and it can be thought of as a lightweight version of XML. Dynamics 365 for Finance and Operations has its own JSON serializer inside the class FormJsonSerializer.
So that we can easily serialize to JSON we have to create two new classes and both are extensions of FormDataContract. The first one is NAMTiles which has a method of type List and can hold several objects of type NAMTile which is our second class. We create several NAMTile objects, group them together in NAMTiles and with a single line of X++ code we serialize and get our JSON string which we then send back over the wire. When it reaches the JavaScript we deserialize with a single line of JS code and start building the tiles inside the control.
1. Create a class called NAMTile. This class represents one tile. Include the following code inside this class:
[DataContractAttribute]
class NAMTile extends FormDataContract
{
    FormProperty m_iTop;
    FormProperty m_iLeft;
    FormProperty m_iTileType;
    FormProperty m_sCaption;
    FormProperty m_sMenu;
    public void new()
    {
        super();
        m_iTop = this.properties().addProperty(methodStr(NAMTile, Top), Types::Integer);
        m_iLeft = this.properties().addProperty(methodStr(NAMTile, Left), Types::Integer);
        m_iTileType = this.properties().addProperty(methodStr(NAMTile, TileType), Types::Integer);
        m_sCaption = this.properties().addProperty(methodStr(NAMTile, Caption), Types::String);
        m_sMenu = this.properties().addProperty(methodStr(NAMTile, Menu), Types::String);
    }
    [DataMemberAttribute("Top")]
    public int Top(int _value = m_iTop.parmValue())
    {
        if(!prmisDefault(_value))
        {
            m_iTop.parmValue(_value);
        }
        return _value;
    }
    [DataMemberAttribute("Left")]
    public int Left(int _value = m_iLeft.parmValue())
    {
        if(!prmisDefault(_value))
        {
            m_iLeft.parmValue(_value);
        }
        return _value;
    }
    [DataMemberAttribute("TileType")]
    public int TileType(int _value = m_iTileType.parmValue())
    {
        if(!prmisDefault(_value))
        {
            m_iTileType.parmValue(_value);
        }
        return _value;
    }
    [DataMemberAttribute("Caption")]
    public str Caption(str _value = m_sCaption.parmValue())
    {
        if(!prmisDefault(_value))
        {
            m_sCaption.parmValue(_value);
        }
        return _value;
    }
    [DataMemberAttribute("Menu")]
    public str Menu(str _value = m_sMenu.parmValue())
    {
        if(!prmisDefault(_value))
        {
            m_sMenu.parmValue(_value);
        }
        return _value;
    }
}
2. Create a class called NAMTiles. This class represents a collection of tiles. Include the following code inside this class:
[DataContractAttribute]
class NAMTiles extends FormDataContract
{
    FormProperty m_oTileList;
    public void new()
    {
        super();
        m_oTileList = this.properties().addProperty(methodStr(NAMTiles, TileList), Types::Class);
        m_oTileList.parmValue(new List(Types::Class));
    }
    [DataMemberAttribute('TileList'), DataCollectionAttribute(Types::Class, classStr(NAMTile))]
    public List TileList(List _value = m_oTileList.parmValue())
    {
        if(!prmisDefault(_value))
        {
            m_oTileList.parmValue(_value);
        }
        return _value;
    }
}
3. Create a method in the X++ runtime class. This method will be called by JavaScript and will return the list of tiles back to JavaScript in JSON format. It does not have any parameters but methods can have parameters of type str, int, etc.
    [FormCommandAttribute("GetTiles")]
    public str GetTiles()
    {
        str sReturn;
        sReturn = FormJsonSerializer::serializeClass(m_oTiles);
        print "sReturn: ", sReturn;
        return sReturn;
    }
4. Modify the init method of the NATForm1 form. Data is hard coded but it could be modified to load from a table.
    public void init()
    {
        super();
        NAMTile oTile;
        int iLeft = 50;
        int iSpacing = 7;
        int iBigWidth = 182;
        int iSmallWidth = 80;
        oTile = new NAMTile();
        oTile.Top(50);
        oTile.Left(iLeft);
        oTile.TileType(2);
        oTile.Caption(SysLabel::labelId2String(literalstr('@SYS336236')));
        oTile.Menu("VendTableListPage");
        NAMNavigationMenuCtrl1.Tiles().TileList().AddEnd(oTile);
        iLeft = iLeft + iBigWidth + iSpacing;
        oTile = new NAMTile();
        oTile.Top(50);
        oTile.Left(iLeft);
        oTile.TileType(3);
        oTile.Caption(SysLabel::labelId2String(literalstr('@SYS117453')));
        oTile.Menu("VendTableHoldListPage");
        NAMNavigationMenuCtrl1.Tiles().TileList().AddEnd(oTile);
        iLeft = iLeft + iSmallWidth + iSpacing;
        oTile = new NAMTile();
        oTile.Top(50);
        oTile.Left(iLeft);
        oTile.TileType(4);
        oTile.Caption(SysLabel::labelId2String(literalstr('@SYS117454')));
        oTile.Menu("VendTablePastDueListPage");
        NAMNavigationMenuCtrl1.Tiles().TileList().AddEnd(oTile);
        iLeft = iLeft + iSmallWidth + iSpacing;
        oTile = new NAMTile();
        oTile.Top(50);
        oTile.Left(iLeft);
        oTile.TileType(3);
        oTile.Caption(SysLabel::labelId2String(literalstr('@SYS191281')));
        oTile.Menu("VendTableDiverseListPage");
        NAMNavigationMenuCtrl1.Tiles().TileList().AddEnd(oTile);
        iLeft = iLeft + iSmallWidth + iSpacing;
        oTile = new NAMTile();
        oTile.Top(50);
        oTile.Left(iLeft);
        oTile.TileType(4);
        oTile.Caption(SysLabel::labelId2String(literalstr('@SYS132272')));
        oTile.Menu("VendExceptionGroup");
        NAMNavigationMenuCtrl1.Tiles().TileList().AddEnd(oTile);
        iLeft = iLeft + iSmallWidth + iSpacing;
        oTile = new NAMTile();
        oTile.Top(50);
        oTile.Left(iLeft);
        oTile.TileType(3);
        oTile.Caption(SysLabel::labelId2String(literalstr('@SYS10022')));
        oTile.Menu("VendGroup");
        NAMNavigationMenuCtrl1.Tiles().TileList().AddEnd(oTile);
        iLeft = iLeft + iSmallWidth + iSpacing;
        oTile = new NAMTile();
        oTile.Top(50);
        oTile.Left(iLeft);
        oTile.TileType(4);
        oTile.Caption(SysLabel::labelId2String(literalstr('@SYS114469')));
        oTile.Menu("VendPriceToleranceGroup");
        NAMNavigationMenuCtrl1.Tiles().TileList().AddEnd(oTile);
        //Purchase orders
        iLeft = 50;
        oTile = new NAMTile();
        oTile.Top(137);
        oTile.Left(iLeft);
        oTile.TileType(1);
        oTile.Caption(SysLabel::labelId2String(literalstr('@SYS336224')));
        oTile.Menu("PurchTableListPage");
        NAMNavigationMenuCtrl1.Tiles().TileList().AddEnd(oTile);
        iLeft = iLeft + iBigWidth + iSpacing;
        oTile = new NAMTile();
        oTile.Top(137);
        oTile.Left(iLeft);
        oTile.TileType(4);
        oTile.Caption(SysLabel::labelId2String(literalstr('@SYS302184')));
        oTile.Menu("PurchTableListPageAssignedToMe");
        NAMNavigationMenuCtrl1.Tiles().TileList().AddEnd(oTile);
        iLeft = iLeft + iSmallWidth + iSpacing;
        oTile = new NAMTile();
        oTile.Top(137);
        oTile.Left(iLeft);
        oTile.TileType(3);
        oTile.Caption(SysLabel::labelId2String(literalstr('@SYS120064')));
        oTile.Menu("PurchTableReceivedNotInvoicedListPage");
        NAMNavigationMenuCtrl1.Tiles().TileList().AddEnd(oTile);
        iLeft = iLeft + iSmallWidth + iSpacing;
        oTile = new NAMTile();
        oTile.Top(137);
        oTile.Left(iLeft);
        oTile.TileType(4);
        oTile.Caption(SysLabel::labelId2String(literalstr('@AccountsPayable:PurchaseAgreements')));
        oTile.Menu("PurchAgreementListPage");
        NAMNavigationMenuCtrl1.Tiles().TileList().AddEnd(oTile);
        iLeft = iLeft + iSmallWidth + iSpacing;
        oTile = new NAMTile();
        oTile.Top(137);
        oTile.Left(iLeft);
        oTile.TileType(3);
        oTile.Caption(SysLabel::labelId2String(literalstr('@GLS109233')));
        oTile.Menu("PlSADTable");
        NAMNavigationMenuCtrl1.Tiles().TileList().AddEnd(oTile);
        iLeft = iLeft + iSmallWidth + iSpacing;
        oTile = new NAMTile();
        oTile.Top(137);
        oTile.Left(iLeft);
        oTile.TileType(4);
        oTile.Caption(SysLabel::labelId2String(literalstr('@AccountsPayable:OpenPrepayments')));
        oTile.Menu("PurchPrepayOpen");
        NAMNavigationMenuCtrl1.Tiles().TileList().AddEnd(oTile);
    }
5. Add the following code to JavaScript after the $dyn.observe function. The m_AddTilesToControl function is responsible for adding the Tiles to the control as soon as GetTiles returns. This function gets a JSON string from GetTiles in X++. The $dyn.callFunction line is responsible for calling the X++ GetTiles method in the X++ runtime class, passing the parameters (empty in this case) and setting the callback function (m_AddTilesToControl).
        self.m_AddTilesToControl = function (_sJSON)
        {
            var oTiles = $.parseJSON(_sJSON, true);
            var i = 0;
            for (i = 0; i <= oTiles.TileList.length - 1; i++)
            {
                var oTile = oTiles.TileList[i];
                var sClass = "tile-big-dark";
                var sSpanClass = "tile-text-big";
                switch(oTile.TileType)
                {
                    case 1:
                        sClass = "tile-big-dark";
                        sSpanClass = "tile-text-big";
                        break;
                    case 2:
                        sClass = "tile-big-light";
                        sSpanClass = "tile-text-big";
                        break;
                    case 3:
                        sClass = "tile-small-dark";
                        sSpanClass = "tile-text-small";
                        break;
                    case 4:
                        sClass = "tile-small-light";
                        sSpanClass = "tile-text-small";
                        break;
                }
                $(element).append("<div class='" + sClass + "' style='left:" + oTile.Left.toString() + "px;top:" + oTile.Top.toString() + "px;'><span class='" + sSpanClass + "'>" + oTile.Caption + "</span></div>");
                console.log("passing");
            }
        }
        var oParams = {};
        $dyn.callFunction(self.GetTiles, self, oParams, self.m_AddTilesToControl);
6. Include these style definitions in the NAMNavigationMenuCtrl.css file:
.tile-big-dark {
    width: 182px;
    height: 80.5px;
    position: absolute;
    background-color: #002050;
}
.tile-big-light {
    width: 182px;
    height: 80.5px;
    position: absolute;
    background-color: #0d62aa;
}
.tile-small-dark {
    width: 80.5px;
    height: 80.5px;
    position: absolute;
    background-color: #002050;
}
.tile-small-light {
    width: 80.5px;
    height: 80.5px;
    position: absolute;
    background-color: #0d62aa;
}
.tile-text-big {
    color: #fff;
    max-width: 180.5px;
    position: absolute;
    bottom: 0;
    left: 0;
    font: normal normal 300 12px/14px 'Segoe UI',tahoma,sans-serif;
    max-height: 35px;
    line-height: 14.1px;
    overflow: hidden;
    margin: 7px 0 2px 0;
    padding: 7px;
    -webkit-line-clamp: 3;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-box-orient: vertical;
}
.tile-text-small {
    color: #fff;
    max-width: 78.5px;
    position: absolute;
    bottom: 0;
    left: 0;
    font: normal normal 300 12px/14px 'Segoe UI',tahoma,sans-serif;
    max-height: 35px;
    line-height: 14.1px;
    overflow: hidden;
    margin: 7px 0 2px 0;
    padding: 7px;
    -webkit-line-clamp: 3;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-box-orient: vertical;
}
7. Press F5 to debug or Debug -> Start Debugging. This is now the new appearance of the control:
Sending Commands From JavaScript to X++
The final part of this tutorial will show you how to invoke actions in X++ code triggered by user actions in the control. In the Navigation Menu control whenever a user clicks on a tile the form that the tile represents will open.
1. Create a global object outside the main JavaScript function. The reason we're creating it outside is because it will contain the click event handler that will be called by the tiles and has to be outside of the scope of the main function.
GlobalObj = {};
(function () {
…
})();
2. Create this JavaScript function immediately after the $dyn.observe function. This function will handle the onclick event from any tile and call the OpenForm method on the X++ runtime class. It can be triggered by the tile itself or by the child span object, so it first checks if the object has an id, if it does not then it is a span object and we need to reference the parent object instead.
        GlobalObj.m_OnClick = function ()
        {
            var oSource = event.srcElement;
            if (oSource.id == "") {
                oSource = oSource.parentElement;
            }
            var oParams = { _sMenu: oSource.id };
            $dyn.callFunction(self.OpenForm, self, oParams);
        }
3. Modify the append method in JavaScript to make the name of the display menu the id for the div and to create the onclick method handler.
$(element).append("<div id='" + oTile.Menu + "' onclick='GlobalObj.m_OnClick();' class='" + sClass + "' style='left:" + oTile.Left.toString() + "px;top:" + oTile.Top.toString() + "px;'><span class='" + sSpanClass + "'>" + oTile.Caption + "</span></div>");
4. Create this method in the X++ runtime class:
    [FormCommandAttribute("OpenForm")]
    public void OpenForm(str _sMenu)
    {
        Args args;
        args = new Args();
        args.caller(this);
        new MenuFunction(_sMenu, MenuItemType::Display).run(args);
    }
5. Press F5 or Debug -> Start Debugging, click on any tile and the associated form will be opened.
Additional Notes
1. Although it isn't demonstrated in this tutorial, it is possible to initiate a call from X++ like in the SendCommandToJavaScript example, and then have the JavaScript execute a $dyn.callFunction in the $dyn.observe function and have the control receive JSON data from the X++ runtime class.
2. Sometimes when calling the $dyn.callFunction method the call will not execute:
Call will not execute during an intreaction
Because the framework is in an interaction. This generally happens when $dyn.callFunction calls are deeply nested in code. The framework will however alert you via the browser's console. When debugging it is always important to monitor the browser's console (you can do this by pressing the F12 key in most browsers). In those cases you must "yield" the JavaScript code. When you "yield" a call you do not call the function directly, you call it via window.setTimeout and you convert a synchronous call into an asynchronous one and the call will then execute when the framework is ready. This is a simple example:
        m_YieldCallFunction = function () {
            var oParams = { };
            $dyn.callFunction(self.SomeMethod, self, oParams);
        }
        window.setTimeout(m_YieldCallFunction, 0);
This article has a more thorough explanation of these two concepts:

Appendix A - NAMNavigationMenuCtrl Final Code
[FormControlAttribute("NAMNavigationMenuCtrl","",classstr(NAMNavigationMenuCtrlBuild))]
class NAMNavigationMenuCtrl extends FormTemplateContainerControl
{
    private FormProperty m_iBackgroundColor;
    private NAMTiles m_oTiles;
    protected void new(FormBuildControl _build, FormRun _formRun)
    {
        super(_build, _formRun);
        this.setResourceBundleName("/resources/html/NAMNavigationMenuCtrl");
        m_iBackgroundColor = this.addProperty(methodstr(NAMNavigationMenuCtrl, BackgroundColor), Types::Integer);
        m_oTiles = new NAMTiles();
    }
    [FormPropertyAttribute(FormPropertyKind::Value, "BackgroundColor")]
    public int BackgroundColor(int _iValue = m_iBackgroundColor.parmValue())
    {
        m_iBackgroundColor.setValueOrBinding(_iValue);
        return m_iBackgroundColor.parmValue();
    }
    public NAMTiles Tiles()
    {
        return m_oTiles;
    }
    [FormCommandAttribute("GetTiles")]
    public str GetTiles()
    {
        str sReturn;
        sReturn = FormJsonSerializer::serializeClass(m_oTiles);
        print "sReturn: ", sReturn;
        return sReturn;
    }
    [FormCommandAttribute("OpenForm")]
    public void OpenForm(str _sMenu)
    {
        Args args;
        args = new Args();
        args.caller(this);
        new MenuFunction(_sMenu, MenuItemType::Display).run(args);
    }
}
Appendix B - NAMNavigationMenuCtrlBuild Final Code
[FormDesignControlAttribute("NAMNavigationMenuCtrl")]
class NAMNavigationMenuCtrlBuild extends FormBuildContainerControl
{
}
Appendix C - NAMNavigationMenuCtrlBuild.css Final Code
.tile-big-dark {
    width: 182px;
    height: 80.5px;
    position: absolute;
    background-color: #002050;
}
.tile-big-light {
    width: 182px;
    height: 80.5px;
    position: absolute;
    background-color: #0d62aa;
}
.tile-small-dark {
    width: 80.5px;
    height: 80.5px;
    position: absolute;
    background-color: #002050;
}
.tile-small-light {
    width: 80.5px;
    height: 80.5px;
    position: absolute;
    background-color: #0d62aa;
}
.tile-text-big {
    color: #fff;
    max-width: 180.5px;
    position: absolute;
    bottom: 0;
    left: 0;
    font: normal normal 300 12px/14px 'Segoe UI',tahoma,sans-serif;
    max-height: 35px;
    line-height: 14.1px;
    overflow: hidden;
    margin: 7px 0 2px 0;
    padding: 7px;
    -webkit-line-clamp: 3;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-box-orient: vertical;
}
.tile-text-small {
    color: #fff;
    max-width: 78.5px;
    position: absolute;
    bottom: 0;
    left: 0;
    font: normal normal 300 12px/14px 'Segoe UI',tahoma,sans-serif;
    max-height: 35px;
    line-height: 14.1px;
    overflow: hidden;
    margin: 7px 0 2px 0;
    padding: 7px;
    -webkit-line-clamp: 3;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-box-orient: vertical;
}
Appendix D - NAMNavigationMenuCtrlBuild.htm Final Code
<script src="/resources/scripts/NAMNavigationMenuCtrl.js"></script>
<link href="/resources/styles/NAMNavigationMenuCtrl.css" rel="stylesheet" type="text/css" />
<div id="NAMNavigationMenuCtrl" data-dyn-bind="sizing: { height: $data.Height, width: $data.Width }, visible: $data.Visible">
</div>
Appendix E - NAMNavigationMenuCtrlBuild.js Final Code
GlobalObj = {};
(function () {
    'use strict';
    $dyn.ui.defaults.NAMNavigationMenuCtrl = {};
    $dyn.controls.NAMNavigationMenuCtrl = function (data, element) {
        var self = this;
        $dyn.ui.Control.apply(self, arguments);
        $dyn.ui.applyDefaults(self, data, $dyn.ui.defaults.NAMNavigationMenuCtrl);
        $(element).css("border", "1px solid black");
        $(element).css("position", "relative");
        $dyn.observe(this.BackgroundColor, function (_iParam) {
            switch (_iParam)
            {
                case 0: //Gray
                    $(element).css("background-color", "#EAEAEA");
                    break;
                case 1: //Light blue
                    $(element).css("background-color", "#C7E0F4");
                    break;
                case 2: //Blue
                    $(element).css("background-color", "#002050");
                    break;
                case 3: //Black
                    $(element).css("background-color", "#000000");
                    break;
            }
        });
        GlobalObj.m_OnClick = function ()
        {
            var oSource = event.srcElement;
            if (oSource.id == "") {
                oSource = oSource.parentElement;
            }
            var oParams = { _sMenu: oSource.id };
            $dyn.callFunction(self.OpenForm, self, oParams);
        }
        self.m_AddTilesToControl = function (_sJSON)
        {
            var oTiles = $.parseJSON(_sJSON, true);
            var i = 0;
            for (i = 0; i <= oTiles.TileList.length - 1; i++)
            {
                var oTile = oTiles.TileList[i];
                var sClass = "tile-big-dark";
                var sSpanClass = "tile-text-big";
                switch(oTile.TileType)
                {
                    case 1:
                        sClass = "tile-big-dark";
                        sSpanClass = "tile-text-big";
                        break;
                    case 2:
                        sClass = "tile-big-light";
                        sSpanClass = "tile-text-big";
                        break;
                    case 3:
                        sClass = "tile-small-dark";
                        sSpanClass = "tile-text-small";
                        break;
                    case 4:
                        sClass = "tile-small-light";
                        sSpanClass = "tile-text-small";
                        break;
                }
                $(element).append("<div id='" + oTile.Menu + "' onclick='GlobalObj.m_OnClick();' class='" + sClass + "' style='left:" + oTile.Left.toString() + "px;top:" + oTile.Top.toString() + "px;'><span class='" + sSpanClass + "'>" + oTile.Caption + "</span></div>");
            }
        }
        var oParams = {};
        $dyn.callFunction(self.GetTiles, self, oParams, self.m_AddTilesToControl);
    }
    $dyn.controls.NAMNavigationMenuCtrl.prototype = $dyn.extendPrototype($dyn.ui.Control.prototype, {});
})();
Appendix F - NAMTile Final Code
[DataContractAttribute]
class NAMTile extends FormDataContract
{
    FormProperty m_iTop;
    FormProperty m_iLeft;
    FormProperty m_iTileType;
    FormProperty m_sCaption;
    FormProperty m_sMenu;
    public void new()
    {
        super();
        m_iTop = this.properties().addProperty(methodStr(NAMTile, Top), Types::Integer);
        m_iLeft = this.properties().addProperty(methodStr(NAMTile, Left), Types::Integer);
        m_iTileType = this.properties().addProperty(methodStr(NAMTile, TileType), Types::Integer);
        m_sCaption = this.properties().addProperty(methodStr(NAMTile, Caption), Types::String);
        m_sMenu = this.properties().addProperty(methodStr(NAMTile, Menu), Types::String);
    }
    [DataMemberAttribute("Top")]
    public int Top(int _value = m_iTop.parmValue())
    {
        if(!prmisDefault(_value))
        {
            m_iTop.parmValue(_value);
        }
        return _value;
    }
    [DataMemberAttribute("Left")]
    public int Left(int _value = m_iLeft.parmValue())
    {
        if(!prmisDefault(_value))
        {
            m_iLeft.parmValue(_value);
        }
        return _value;
    }
    [DataMemberAttribute("TileType")]
    public int TileType(int _value = m_iTileType.parmValue())
    {
        if(!prmisDefault(_value))
        {
            m_iTileType.parmValue(_value);
        }
        return _value;
    }
    [DataMemberAttribute("Caption")]
    public str Caption(str _value = m_sCaption.parmValue())
    {
        if(!prmisDefault(_value))
        {
            m_sCaption.parmValue(_value);
        }
        return _value;
    }
    [DataMemberAttribute("Menu")]
    public str Menu(str _value = m_sMenu.parmValue())
    {
        if(!prmisDefault(_value))
        {
            m_sMenu.parmValue(_value);
        }
        return _value;
    }
}
Appendix G - NAMTiles Final Code
[DataContractAttribute]
class NAMTiles extends FormDataContract
{
    FormProperty m_oTileList;
    public void new()
    {
        super();
        m_oTileList = this.properties().addProperty(methodStr(NAMTiles, TileList), Types::Class);
        m_oTileList.parmValue(new List(Types::Class));
    }
    [DataMemberAttribute('TileList'), DataCollectionAttribute(Types::Class, classStr(NAMTile))]
    public List TileList(List _value = m_oTileList.parmValue())
    {
        if(!prmisDefault(_value))
        {
            m_oTileList.parmValue(_value);
        }
        return _value;
    }
}

Upload data from Excel in D365FO X++

 Action Menu Item: SAN_UploadExcelData Object type: Class Object: <Controller class name> Label: <> Class: Controller class clas...