Custom HTML control to receive JavaScript messages

Implementation of custom iframe control for D365 F&SCM with possibility to receive custom JavaScript messages from its content. For example, D365F&SCM needs to get some information from iframe when the user completes the process on the third-party website/application.

Build class

/// <summary>
/// Defines the design time experience for the VKIframe.
/// </summary>
[FormDesignControlAttribute('VKIframeControl')]
[FormDesignValidContainerControlAttribute(classstr(FormTabPageControl))]
[FormDesignValidContainerControlAttribute(classstr(FormGroupControl))]
public class VKIframeBuildControl extends FormBuildControl
{
    str url;
    str title;

    public void new(FormContainer _container)
    {
        super(_container);
    }

    [FormDesignProperty('URL', 'Behavior')]
    public str url(str _value = '')
    {
        if (!prmisdefault(_value))
        {
            url = _value;
        }

        return url;
    }

    [FormDesignProperty('Title', 'Behavior')]
    public str title(str _value = '')
    {
        if (!prmisdefault(_value))
        {
            title = _value;
        }

        return title;
    }
}

Run time class

/// <summary>
///     VKIframe control
/// </summary>
[FormControlAttribute('VKIframeControl', '/resources/html/VKIframeHTML.html', classStr(VKIframeBuildControl))]
public class VKIframeControl extends FormTemplateControl
{
    private FormProperty Url;
    private FormProperty Title;

    public void new(FormBuildControl _build, FormRun _formRun)
    {
        super(_build, _formRun);

        this.setTemplateId(classStr(VKIframeControl));
        this.setResourceBundleName('/Resources/HTML/VKIframeHTML.html');
        
        Url = properties.addProperty(methodStr(VKIframeControl, url), Types::String);
        Title = properties.addProperty(methodStr(VKIframeControl, title), Types::String);

    }

    [FormPropertyAttribute(FormPropertyKind::BindableValue, identifierStr(Url), true)]
    public str url(str _url = Url.parmValue())
    {
        if (!prmIsDefault(_url))
        {
            Url.parmValue(_url);
        }

        return Url.parmValue();
    }

    [FormPropertyAttribute(FormPropertyKind::BindableValue, identifierStr(Title), true)]
    public str title(str _title = Title.parmValue())
    {
        if (!prmIsDefault(_title))
        {
            Title.parmValue(_title);
        }

        return Title.parmValue();
    }

    /// <summary>
    /// Applies the build configurations to the run instance of the control.
    /// </summary>
    /// <remarks>
    /// ApplyBuild of the form part control should be called only in formRun::Init() method.
    /// </remarks>
    public void applyBuild()
    {
        super();

        var build = this.build() as VKIframeBuildControl;

        if (build)
        {
            this.url(build.url());
            this.title(build.title());
        }
    }

    [FormCommandAttribute(identifierstr(UpdatePaymentStatus), true)]
    public void updatePaymentStatus(str serializedJson)
    {
        // this method will be also available on the form control level
        info('updatePaymentStatus: ' + serializedJson);
    }

}
updatePaymentStatus method will also be available at the form control level:

HTML template

<script src="/resources/scripts/VKIframeJS.js"></script>
<div id="VKIframeControl" class="iframeControl fill-width" style="overflow:hidden" data-dyn-bind="sizing: { width: $dyn.layout.Size.available, height: $dyn.layout.Size.available }">
    <iframe class="iframeControl-frame" style="border:0px; width:100%; height:100%" sandbox="allow-scripts allow-forms allow-same-origin allow-popups allow-popups-to-escape-sandbox" data-dyn-bind="title: $data.Title"></iframe>
</div>

JavaScript

(function () {
    'use strict';
    Globalize.addCultureInfo('en', {
        messages: {
           IFrame_iFrameTitle: "Payment",
        }
    });
    $dyn.controls.VKIframeControl = function (data, element) {
        $dyn.ui.Control.apply(this, arguments);

        if (data.Url) {
            var self = this;
            $dyn.bulkObserve({ url: data.Url },
                function (changes) {
                    self.onPropChanged(changes.url);
                }, self.Updating, self);
        }
    };

    $dyn.controls.VKIframeControl.prototype = $dyn.extendPrototype($dyn.ui.Control.prototype, {
        Url: undefined,
        Title: undefined,

        init: function () {
            $dyn.ui.Control.prototype.init.apply(this, arguments);
            if (this.Title === undefined) {
                this.Title($dyn.label('IFrame_iFrameTitle'));
            }
            
            if (window.addEventListener) {
                window.addEventListener("message", this.applyContext(this.onMessageReceive, this), false);        
            } 
            else if (window.attachEvent) {
                window.attachEvent("onmessage", this.applyContext(this.onMessageReceive, this), false);
            }
        },

        onPropChanged: function (urlChanged) {
            var webHostIframe = this.getIFrameElement();

            if (urlChanged && webHostIframe) {
                webHostIframe.setAttribute('src', this.Url());
            }
        },

        getIFrameElement: function () {
            var webHostIframe = undefined;
            var iframes = this.element.getElementsByTagName('iframe');
            if (iframes && iframes.length) {
                webHostIframe = iframes[0]; //There is only one iframe in webHost control
            }

            return webHostIframe;
        },
        
        applyContext: function (Func, Context) {
            return function () {
                return Func.apply(Context, arguments);
            }
        },
        
        onMessageReceive: function (event, ui) {
          
            var data = event.data;
             
            if ($dyn.callFunction) {
                $dyn.callFunction(this.UpdatePaymentStatus, this, [data], function () { /* executes on success */ });
            }
        }
    });
})();
Resources:
This is how child window (from iframe) can send message:
<html>
<body>
<script>
function clickMe() {
    console.log('iFrame: clickMe');
    
    window.parent.postMessage({
        'code':          'transIdent',
        'paymentStatus': 'Paid',    
        'amount':        10.56,
        'description':   'SO-000405',
        'currency':      'GBP'
    }, "*");
}
</script>
<a href="javascript: clickMe();">click me</a>
</body>
</html>


 

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 and "buy me a coffee" link.
Join us.

Blog Tags