OTRS API Reference JavaScript

Source: Core.UI.Floater.js

// --
// Copyright (C) 2001-2020 OTRS AG, https://otrs.com/
// --
// This software comes with ABSOLUTELY NO WARRANTY. For details, see
// the enclosed file COPYING for license information (GPL). If you
// did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
// --

"use strict";

var Core = Core || {};
Core.UI = Core.UI || {};

/**
 * @namespace Core.UI.Floater
 * @memberof Core.UI
 * @author OTRS AG
 * @description
 *      This namespace contains the Floater code.
 */
Core.UI.Floater = (function (TargetNS) {

    /**
     * @private
     * @name InitFloaters
     * @memberof Core.UI
     * @function
     * @description
     *      This function initializes iframe floaters on links with certain trigger attributes. To provide
     *      a floater on a link (<a>) element, just add data-trigger="floater" to any <a> which has
     *      a non-empty href attribute. Its also possible to add the data attribute on runtime.
     */
    TargetNS.Init = function() {

        var FloaterHoverTimer = {},
            FloaterTriggerHoverTimer = {};

        /**
         * @private
         * @name CreateFloaterOpenTimeout
         * @memberof Core.UI.Floater.Init
         * @function
         * @param {jQueryObject} $Element
         * @param {Function} TimeoutFunction
         * @description
         *      This function sets the Timeout for closing a floater.
         */
        function CreateFloaterOpenTimeout($Element, TimeoutFunction) {
            FloaterTriggerHoverTimer[$Element] = setTimeout(TimeoutFunction, 500);
        }

        /**
         * @private
         * @name ClearFloaterOpenTimeout
         * @memberof Core.UI.Floater.Init
         * @function
         * @param {jQueryObject} $Element
         * @description
         *      This function clears the Timeout for a floater.
         */
        function ClearFloaterOpenTimeout($Element) {
            if (typeof FloaterTriggerHoverTimer[$Element] !== 'undefined') {
                clearTimeout(FloaterTriggerHoverTimer[$Element]);
            }
        }

        /**
         * @private
         * @name CreateFloaterOpenTimeout
         * @memberof Core.UI.Floater.Init
         * @function
         * @param {jQueryObject} $Element
         * @param {Function} TimeoutFunction
         * @description
         *      This function sets the Timeout for closing a floater.
         */
        function CreateFloaterCloseTimeout($Element, TimeoutFunction) {
            FloaterHoverTimer[$Element] = setTimeout(TimeoutFunction, 500);
        }

        /**
         * @private
         * @name ClearFloaterCloseTimeout
         * @memberof Core.UI.Floater.Init
         * @function
         * @param {jQueryObject} $Element
         * @description
         *      This function clears the Timeout for a floater.
         */
        function ClearFloaterCloseTimeout($Element) {
            if (typeof FloaterHoverTimer[$Element] !== 'undefined') {
                clearTimeout(FloaterHoverTimer[$Element]);
            }
        }

        /**
         * @private
         * @name RemoveActiveFloater
         * @memberof Core.UI.Floater.Init
         * @function
         * @param {jQueryObject} $FloaterObj
         * @description
         *      This function clears the Timeout for a floater.
         */
        function RemoveActiveFloater($FloaterObj) {

            if (!$FloaterObj) {
                $FloaterObj = $('div.MetaFloater:visible');
            }

            $('[data-trigger="floater"]').removeClass('FloaterOpen');
            $FloaterObj.fadeOut('fast', function() {
                $(this).remove();
                ClearFloaterCloseTimeout($(this));
            });
        }

        // init floaters
        $('body').off('click.FloaterClose').on('click.FloaterClose', '.MetaFloater > a.Close', function() {
            RemoveActiveFloater($(this).closest('.MetaFloater'));
            return false;
        });

        $('body').off('click.FloaterScale').on('click.FloaterScale', '.MetaFloater > a.Scale', function() {

            var $IconObj = $(this).find('i'),
                $IframeObj = $(this).closest('.MetaFloater').find('iframe');

            if ($IconObj.hasClass('fa-search-plus')) {
                $IconObj.removeClass('fa-search-plus').addClass('fa-search-minus');
                $IframeObj.addClass('NoScale');
            }
            else {
                $IconObj.removeClass('fa-search-minus').addClass('fa-search-plus');
                $IframeObj.removeClass('NoScale');
            }

            return false;
        });

        $('body').off('mouseenter.Floater').on('mouseenter.Floater', 'div.MetaFloater', function() {
            ClearFloaterCloseTimeout($(this));
        });

        $('body').off('mouseleave.Floater').on('mouseleave.Floater', 'div.MetaFloater', function() {
            CreateFloaterCloseTimeout($(this), function () {
                RemoveActiveFloater();
            });
        });

        $('body').off('mouseleave.FloaterTrigger').on('mouseleave.FloaterTrigger', '[data-trigger="floater"]', function(Event) {
            ClearFloaterOpenTimeout($(Event.target));
            CreateFloaterCloseTimeout($(this), function () {
                RemoveActiveFloater();
            });
        });

        $('body').off('mouseenter.FloaterTrigger').on('mouseenter.FloaterTrigger', '[data-trigger="floater"]', function(Event) {

            ClearFloaterCloseTimeout($(this));

            // Set Timeout for opening floater
            CreateFloaterOpenTimeout($(Event.target), function () {

                var $TriggerObj    = $(Event.target),
                    TriggerOffset  = $TriggerObj.offset(),
                    ViewportWidth  = parseInt($(window).width(), 10),
                    AvailableHeightBottom,
                    AvailableHeightTop,
                    FloaterWidth,
                    $FloaterObj,
                    Margin = 25,
                    iFrameURL = $TriggerObj.data('floater-url'),
                    FloaterTemplate = Core.Template.Render('MetaFloater');

                if (!iFrameURL) {
                    return false;
                }

                // don't open the floater again if there is one already
                if ($TriggerObj.hasClass('FloaterOpen')) {
                    return false;
                }

                $('[data-trigger="floater"]').removeClass('FloaterOpen');
                $TriggerObj.addClass('FloaterOpen');

                $FloaterObj = $(FloaterTemplate);

                // only one floater at the same time, so close other ones
                $('body > div.MetaFloater:visible').remove();

                // show floater to be able to calculate its width
                $FloaterObj.appendTo('body').css('display', 'none');

                // calculate floater dimensions
                FloaterWidth = parseInt($FloaterObj.outerWidth(), 10);

                // set left position
                if (TriggerOffset.left - ViewportWidth / 50 < FloaterWidth / 2) {

                    // case 1: trigger element on far left side without enough space to show the floater
                    $FloaterObj.addClass('Left').css({
                        right: 'auto',
                        left: TriggerOffset.left
                    });
                }
                else if (ViewportWidth - TriggerOffset.left + ($TriggerObj.outerWidth() / 2) - ViewportWidth / 50 < FloaterWidth / 2) {

                    // case 2: trigger element on far right side
                    $FloaterObj.addClass('Right').css({
                        right: 'auto',
                        left: TriggerOffset.left - FloaterWidth + $TriggerObj.outerWidth()
                    });
                }
                else {

                    // Default position: Centered relative to the trigger element
                    $FloaterObj.addClass('Center').css({
                        right: 'auto',
                        left: parseInt(TriggerOffset.left + ($TriggerObj.outerWidth() / 2) - FloaterWidth / 2, 10)
                    });
                }

                // calculate available height to bottom of page
                AvailableHeightBottom = parseInt($(window).scrollTop() + $(window).height() - (TriggerOffset.top + $TriggerObj.outerHeight()), 10);

                // calculate available height to top of page
                AvailableHeightTop = parseInt(TriggerOffset.top - $(window).scrollTop(), 10);

                // decide whether list should be positioned on top or at the bottom of the input field
                if (AvailableHeightTop > AvailableHeightBottom) {
                    $FloaterObj.addClass('Bottom').css({
                        top: 'auto',
                        bottom: parseInt($('body').height() - TriggerOffset.top, 10) + Margin
                    });
                }
                else {
                    $FloaterObj.addClass('Top').css({
                        top: parseInt(TriggerOffset.top + $TriggerObj.outerHeight(), 10) + Margin,
                        bottom: 'auto'
                    });
                }

                $FloaterObj.find('iframe').attr('src', iFrameURL);
                $FloaterObj.find('a.Open').attr('href', $TriggerObj.attr('href'));
                $FloaterObj.fadeIn();

                $('div.MetaFloater:visible iframe').off('load.MetaFloater').on('load.MetaFloater', function() {
                    $(this).closest('.Content').fadeIn('fast', function() {
                        var $ActiveFloaterObj = $('div.MetaFloater:visible');
                        $ActiveFloaterObj
                            .children('div.NoPreview, a.Scale, a.Open').fadeIn()
                            .parent()
                            .children('i').remove();
                    });
                });
            });
        });
    }

    Core.Init.RegisterNamespace(TargetNS, 'APP_GLOBAL');

    return TargetNS;
}(Core.UI.Floater || {}));