function LtiConsumerXBlock(runtime, element) {
    $(function ($) {
        // Adapted from leanModal v1.1 by Ray Stone - http://finelysliced.com.au
        // Dual licensed under the MIT and GPL
        // Renamed leanModal to iframeModal to avoid clash with platform-provided leanModal
        // which removes the href attribute from iframe elements upon modal closing
        $.fn.extend({
            iframeModal: function (options) {
                var $trigger = $(this);
                var modal_id = $trigger.data("target");
                var defaults = { top: 100, overlay: 0.5, closeButton: null };
                var overlay_id = (modal_id + '_lean-overlay').replace('#', '');
                var overlay = $("<div id='" + overlay_id + "' class='lean-overlay'></div>");
                $("body").append(overlay);
                options = $.extend(defaults, options);
                return this.each(function () {
                    var o = options;

                    var $modal = $(modal_id);
                    // If we are already in an iframe, skip creation of the modal, since
                    // it won't look good, anyway. Instead, we post a message to the parent
                    // window, requesting creation of a modal there.
                    // This is used by the courseware microfrontend.
                    if (window !== window.parent) {
                        // LTI 1.1 launch URLs are XBlock handler URLs that point to the lti_launch_handler method of
                        // the XBlock. When we get the handler URL from the runtime, it returns a relative URL without a
                        // protocol or hostname. However, in LTI 1.3, the launch URLs come from user input, including
                        // the values of fields on the XBlock or fields in the database. These URLs should be absolute
                        // URLs with a protocol and hostname, so we should not prepend a protocol and hostname to those
                        // URLs.
                        var launch_url = $modal.data('launch-url');

                        if (ltiVersion === 'lti_1p1') {
                            launch_url = window.location.origin + launch_url
                        }

                        window.parent.postMessage(
                            {
                                'type': 'plugin.modal',
                                'payload': {
                                    'url': launch_url,
                                    'title': $modal.find('iframe').attr('title'),
                                    'width': $modal.data('width')
                                }
                            },
                            document.referrer
                        );
                        return;
                    }

                    // Set iframe src attribute to launch LTI provider.
                    $modal.find('iframe').attr('src', $modal.data('launch-url'));
                    $("#" + overlay_id).click(function () {
                        close_modal(modal_id)
                    });
                    $(o.closeButton).click(function () {
                        close_modal(modal_id)
                    });
                    var modal_height = $(modal_id).outerHeight();
                    var modal_width = $(modal_id).outerWidth();
                    $("#" + overlay_id).css({ "display": "block", opacity: 0 });
                    $("#" + overlay_id).fadeTo(200, o.overlay);
                    $(modal_id).css({
                        "display": "block"
                    });
                    $(modal_id).fadeTo(200, 1);
                    $(modal_id).attr('aria-hidden', false);
                    $('body').css('overflow', 'hidden');

                        e.preventDefault();

                        /* Manage focus for modal dialog */
                        /* Set focus on close button */
                        $(o.closeButton).focus();

                        /* Redirect close button to iframe */
                        $(o.closeButton).on('keydown', function (e) {
                           if (e.which === 9) {
                               e.preventDefault();
                               // This is a workaround due to Firefox triggering focus calls oddly.
                               setTimeout(function () {
                                   $modal.find('iframe')[0].contentWindow.focus();
                               }, 1);
                           }
                        });

                    /* Redirect non-iframe tab to close button */
                    var $inputs = $('select, input, textarea, button, a').filter(':visible').not(o.closeButton);
                    $inputs.on('focus', function (e) {
                        e.preventDefault();
                        $(options.closeButton).focus();
                    });

                });
                function close_modal(modal_id) {
                    var $modal = $(modal_id);
                    $('select, input, textarea, button, a').off('focus');
                    $("#" + overlay_id).fadeOut(200);
                    $modal.css({ "display": "none" });
                    $modal.attr('aria-hidden', true);
                    $modal.find('iframe').attr('src', '');
                    $('body').css('overflow', 'auto');
                    $trigger.focus();
                }
            }
        });

        function confirmDialog(message, triggerElement, showCancelButton) {
            var def = $.Deferred();

            // In order to scope the dialog container to the lti-consumer-container, grab the ID of the
            // lti-consumer-container ancestor and append it to the ID of the dialog container.
            var container_id = triggerElement.closest(".lti-consumer-container").attr("id");
            var dialog_container_id = "dialog-container-" + container_id;

            // Hide the button that triggered the event, i.e. the launch button.
            triggerElement.hide();

            $('<div id="' + dialog_container_id + '"></div>').insertAfter(triggerElement) // TODO: this will need some cute styling. It looks like trash but it works.
                .append('<p>' + message + '</p>')

            var $dialog_container = $("#" + dialog_container_id);

            if (showCancelButton) {
                $dialog_container
                .append('<button style="margin-right:1rem" id="cancel-button">Cancel</button>');
            }
            $dialog_container.append('<button id="confirm-button">OK</button>');

            // When a learner clicks "OK" or "Cancel" in the consent dialog, remove the consent dialog, show the launch
            // button, and resolve the promise.
            $dialog_container.find('#confirm-button').click(function () {
                // Show the button that triggered the event, i.e. the launch button.
                triggerElement.show();
                $dialog_container.remove()
                $('body').append('<h1>Confirm Dialog Result: <i>Yes</i></h1>');
                def.resolve("OK");
            })
            $dialog_container.find('#cancel-button').click(function () {
                // Hide the button that triggered the event, i.e. the launch button.
                triggerElement.show()
                $dialog_container.remove()
                $('body').append('<h1>Confirm Dialog Result: <i>No</i></h1>');
                def.resolve("Cancel");
            })
            return def.promise();
        };

        var $element = $(element);
        var $ltiContainer = $element.find('.lti-consumer-container');
        var askToSendUsername = $ltiContainer.data('ask-to-send-username') == 'True';
        var askToSendEmail = $ltiContainer.data('ask-to-send-email') == 'True';
        var ltiVersion = $ltiContainer.data('lti-version');

        function renderPIIConsentPromptIfRequired(onSuccess, showCancelButton=true) {
            if (askToSendUsername && askToSendEmail) {
                msg = "Click OK to have your username and e-mail address sent to a 3rd party application.";
            } else if (askToSendUsername) {
                msg = "Click OK to have your username sent to a 3rd party application.";
            } else if (askToSendEmail) {
                msg = "Click OK to have your e-mail address sent to a 3rd party application.";
            } else {
                onSuccess("OK");
                return;
            }

            if (showCancelButton) {
                msg += "\n\nClick Cancel to return to this page without sending your information.";
            }

            msg = gettext(msg);
            $.when(confirmDialog(msg, $(this), showCancelButton)).then(onSuccess);
        }

        // Render consent dialog for inline elements immediately.
        var $ltiIframeContainerElement = $element.find('#lti-iframe-container');
        $ltiIframeContainerElement.each(function () {
            var ltiIframeTarget = $ltiIframeContainerElement.data('target')
            renderPIIConsentPromptIfRequired.apply(this, [
                function (status) {
                    if (status === 'OK') {
                        // After getting consent to share PII, set the src attribute of the iframe to start the launch.
                        $ltiIframeContainerElement.find('iframe').attr('src', ltiIframeTarget);
                    }
                },
                false
            ]);
        })

        // Apply click handler to modal launch button.
        var $ltiModalButton = $element.find('.btn-lti-modal');
        $ltiModalButton.click(function () {
            renderPIIConsentPromptIfRequired.apply(this, [
                function (status) {
                    if (status === 'OK') {
                        $ltiModalButton.iframeModal({
                            top: 200, closeButton: '.close-modal'
                        })
                    }
                }
            ]);
        });

        // Apply click handler to new window launch button.
        var $ltiNewWindowButton = $element.find('.btn-lti-new-window');
        $ltiNewWindowButton.click(function () {
            renderPIIConsentPromptIfRequired.apply(this, [
                function (status) {
                    if (status == "OK") {
                        window.open(
                            $ltiNewWindowButton.data('target')
                        );
                    }
                }
            ]);
        });
    });
}