I had a task to copy the product items numbers separated by comma that are shown on the grid to the to the clipboard when an User clicks the specific button. Also I would like to assign a hot key to this button. Both tasks have been implemented with the help of Extensible control see details below.
Why Extensible control? It is because the clipboard and hot keys are client side resources. To implement the client side logic X++ can not be used, since it has no access to the client’s resources.
In my case I use X++ to prepare a string that contains product items separated by comma that is shown on the form grid, while the Extensible control is used to add client side logic to copy the string to the clipboard and assign hot key.
To simplify the X++ logic lets assume there is a form with a released products grid with one CopyButton button. The CopyButton button should copy product ids separated by comma to the clipboard.
There is no code in the CopyButton.clicked() method.
Create a new PIMCopyControl extensible control.
Create a new class that is inherited form the FormTemplateControl class:
[FormControlAttribute('PIMCopyControl','/Resources/html/PIMCopyControl', classStr(PIMCopyControlBuild))]
internal final class PIMCopyControl extends FormTemplateControl
{
public void new(FormBuildControl _build, FormRun _formRun)
{
super(_build, _formRun);
this.setTemplateId('PIMCopyControl');
this.setResourceBundleName('/Resources/html/PIMCopyControl');
}
[FormCommandAttribute("getProducts")]
public str getProducts()
{
Object formObject = this.formRun();
str products = formObject.copySelectedProducts();
return products;
}
}
Create a new class that is inherited form the FormBuildControl class. It allows you to add the new control on the form.
[FormDesignControlAttribute('PIMCopyControl')]
internal final class PIMCopyControlBuild extends FormBuildControl
{
}
Add the extensible control to the form.
Create an empty files with .htm and .js file extension. Add new Resources to the project, select the file on the Add new item form and repeat adding with another file.
Add new code to the .htm file:
<script src="/resources/scripts/PIMCopyControl.js"></script>
<div id="PIMCopyControl" class="copyControl" data-dyn-bind="visible: $data.Visible"></div>
The htm file contains an empty <div> with several attributes. It works without this <div> as well, but error behind the scene is generated by the Dynamics365F&O JavaScript function that control doesn’t have a template.
Add new code the the .js file:
(function () {
'use strict';
$dyn.ui.defaults.PIMCopyControl = {};
$dyn.controls.PIMCopyControl = function (data, element) {
var self = this;
$dyn.ui.Control.apply(self, arguments);
$dyn.ui.applyDefaults(self, data, $dyn.ui.defaults.PIMCopyControl);
self.getProductsCallBack = function (_sJSON) {
navigator.clipboard.writeText(_sJSON);
}
}
$dyn.controls.PIMCopyControl.prototype = $dyn.extendPrototype($dyn.ui.Control.prototype,
{
init: function (data, element) {
var self = this;
$dyn.ui.Control.prototype.init.apply(this, arguments);
var elemm = document.getElementsByName('CopyButton')[0];
elemm.onclick = function () {
$dyn.callFunction(self.getProducts, self, {}, self.getProductsCallBack);
};
}
});
// Keep track of clicked keys
var isKeyPressed = {
q: false, // ASCII code for 'q'
w: false // ASCII code for 'w'
// ... Other keys to check for custom key combinations
};
document.onkeydown = keyDownEvent => {
//Prevent default key actions, if desired
//keyDownEvent.preventDefault();
// Track down key click
if (keyDownEvent.key == "q" || keyDownEvent.key == "w") {
isKeyPressed[keyDownEvent.key] = true;
}
// Check described custom shortcut
if (isKeyPressed["q"] && isKeyPressed["w"]) {
var elemm = document.getElementsByName('CopyButton')[0];
if (elemm) { elemm.click(); }
}
};
document.onkeyup = keyUpEvent => {
// Prevent default key actions, if desired
//keyUpEvent.preventDefault();
// Track down key release
if (keyUpEvent.key == "q" || keyUpEvent.key == "w") {
isKeyPressed[keyUpEvent.key] = false;
}
};
})();
The JavaScript function will be called when the form with extensible control is loaded at first time. Each time the form is opened the DOM is rebuild. The control prototype Init method is called each time the form is opened. So the Init method is used to add a custom logic.
The $dyn.callFunction(funcName) is used to call serves side (X++) function. The self.funcNameCallBack is used to handle the result of the called function (the _sJSON variable contains the variable returned by the X++ method).
The $dyn class contains javascripts classes that are implemented to visualize standard D365 F&O in a browser.
To access the CopyButton the following code is used: document.getElementsByName(‘CopyButton’)[0]
There are other articles about extensible control:
Extensible Control in D365 F&O – Sami’s blog on Dynamics AX (wordpress.com)
Part 1: Extensible control – X++ classes – Goshoom.NET Dev Blog
Part 2: Extensible control – HTML/JavaScript – Goshoom.NET Dev Blog
The standard CopyButton button is used just to create an UI element on the form (to avoid creating this element via html, css manually in Extensible control).
The Extensible control is used to add a business logic to the CopyButton button. The Extensible control is just an empty <div> block on UI.