component-overlay.js

/* global mejs:false */
/**
 * open content in overlay
 * @module overlay
 * @param  {jQuery} $ Instance of jQuery
 * @return {Object} List of overlay methods
 */
XA.component.overlay = function ($) {
    /**
    * This object stores all public api methods
    * @type {Object.<Methods>}
    * @memberOf module:overlay
    * */
    var api = {},
        href = window.location.href,
        host = location.host,
        label,
        overlayPlaceholder = false,
        marginTop = 100,
        focusableElementsString = 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex="0"], [contenteditable]',
        firstTabStop,
        lastTabStop,
        overlayClickSource;
    /**
    * isPreviewMode check if page in preview mode
    * @memberOf module:overlay
    * @private
    * @returns {Boolean} is page in a preview mode
    */
    function isPreviewMode() {
        if (href.indexOf("sc_mode=preview") > -1) {
            return true;
        }
        var $hdPageMode = $('#hdPageMode');

        return $hdPageMode.length > 0 && $hdPageMode.attr('value') == 'preview';
    }


    /**
     * isOverlayPage check if "#wrapper" element has class "overlay-page"
     * @memberOf module:overlay
     * @private
     * @returns {Boolean} is #wrapper an overlay page
     */
    function isOverlayPage() {
        return $('#wrapper').hasClass('overlay-page');
    }
    /**
     * hasOverlayContent check if "overlay-source" 
     * element present on a page
     * @memberOf module:overlay
     * @private
     * @returns {Boolean} is overlay-source present on a page
     */
    function hasOverlayContent() {
        return $(".overlay-source").length;
    }
    /**
    * resizeOverlay resize overlay
    * @memberOf module:overlay
    * @param {jQuery<Object>} inner ".overlay-inner"
    * @param {jQuery<Object>} content (".component-content"),
    * @param {Object} options contains size of overlay 
    * @param {number} [options.width] width of overlay
    * @param {number} [options.height] height of overlay
    * @param {string} [options.percent] units of width and height
    * @alias module:overlay.resizeOverlay
    */
    api.resizeOverlay = function (inner, content, options) {
        var unit = "px";
        var css = {
            "width": "",
            "height": ""
        };
        var wh = $(window).height();

        if (options.percent) {
            unit = "%";
            inner.addClass("overlay-percent");
        } else {
            inner.removeClass("overlay-percent");
        }

        if (options.width) {
            css["width"] = options.width + unit;
        }
        if (options.height) {
            css["height"] = options.height + unit;
        }

        css["max-height"] = (wh - marginTop - 0.1 * wh) + "px";

        content.css(css);
    }
    /**
    * getUrlVariables get variables provided in overlay url
    * @memberOf module:overlay
    * @param {String} url url of overlay content
    * @private
    * @returns {Object} list of get parameters
    */
    function getUrlVariables(url) {
        var q = url.split('?')[1],
            vars = [],
            hash;

        if (q != undefined) {
            q = q.split('&');
            for (var i = 0; i < q.length; i++) {
                hash = q[i].split('=');
                vars.push(hash[1]);
                vars[hash[0]] = hash[1];
            }
        }

        return vars;
    }
    /**
    * getSize check if overlay has width or height in url 
    * and set up this values  
    * @memberOf module:overlay
    * @param {String} vars variables from url of overlay content
    * @private
    * @returns {Object} sizes of overlay
    */
    function getSize(vars) {
        var obj = {};

        if (vars["width"] !== null) {
            obj["width"] = vars["width"];
        }

        if (vars["height"] !== null) {
            obj["height"] = vars["height"];
        }

        return obj;
    }
    /**
    * checkInternal checks if overlay content is internal resource
    * @memberOf module:overlay
    * @param {String} url url of overlay content
    * @private
    * @returns {Boolean} true - content is internal| false - content is external
    */
    function checkInternal(url) {
        if (url.indexOf(host) > -1) {
            return true;
        }
        return false;
    }
    /**
    * checkImage checks if overlay content is an image
    * @memberOf module:overlay
    * @param {String} url url of overlay content
    * @private
    * @returns {Boolean} true - content is an image| false - content not an a image
    */
    function checkImage(url) {
        var ext = url.split("?")[0].split('.').pop();
        if ($.inArray(ext, ['gif', 'png', 'jpg', 'jpeg']) > -1) {
            return true;
        }

        return false;
    }

    /**
    * loadOverlay creates overlay content and show it
    * @memberOf module:overlay
    * @param {String} url url of overlay
    * @param {jQuery<DOMElement>} overlay overlay DOMElement
    * @private
    */
    function loadOverlay(url, overlay) {
        var content = overlay.find(".overlay-inner"),
            overlayContent = overlay.find(".component-content"),
            vars = getUrlVariables(url),
            internalLink = checkInternal(url),
            overlaySize = getSize(vars),
            suffix;
        overlayContent.addClass('overlayFullWidth');
        content.removeAttr('style');
        if (internalLink) {
            if (checkImage(url)) {
                content.empty().append($("<img>", { src: url }));
                content.css(overlaySize);
                overlayContent.removeClass('overlayFullWidth');
                showOverlay(overlay);
            } else if (url.indexOf("overlaytype=iframe") > -1) {
                content.empty().append($("<iframe>", { src: url, style: "width: 100%; height: 100%" }));
                content.css(overlaySize);
                showOverlay(overlay);
            } else {
                if (isPreviewMode()) {
                    suffix = "cf_overlay=1";
                    url = url.replace("sitecore/shell/");
                    url += (url.indexOf("?") == -1 ? "?" : "&") + suffix;
                }

                $.get(url, function (data) {
                    var overlayData = $(data).before().first();

                    api.resizeOverlay(content, overlayContent, {
                        width: overlayData.attr("data-width"),
                        height: overlayData.data("height"),
                        percent: overlayData.data("percent")
                    });

                    content.empty().append(data);
                    XA.init();
                    showOverlay(overlay);
                });
            }
        } else {
            if (checkImage(url)) {
                content.empty().append($("<img>", { src: url }));
            } else {
                content.empty().append($("<iframe >", { src: url, style: "width: 100%; height: 100%" }));
            }
            content.css(overlaySize);
            showOverlay(overlay);
        }

    }
    /**
    * preShowOverlay sets opacity to 1 and shows overlay
    * @memberOf module:overlay
    * @param {jQuery<DOMElement>} overlay overlay DOMElement
    * @private
    */
    function preShowOverlay(overlay) {
        overlay.css({
            "opacity": 1
        }).show();
    }
    /**
    * showOverlay makes overlay visible
    * @memberOf module:overlay
    * @param {jQuery<DOMElement>} overlay overlay DOMElement
    * @private
    * @returns {jQuery<DOMElement>} overlay overlay DOMElement
    */

    function showOverlay(overlay) {
        var close, content;
        overlay.show().animate({
            opacity: 1
        });

        // hides wrapper data from screen readers
        $("#wrapper").addClass("aria-hidden");

        overlayClickSource = document.activeElement;

        close = overlay.find(".overlay-close");
        setTimeout(function () { close.focus(); }, 0);

        content = overlay.find(".overlay-inner");
        content.blur(function (args) {
            args.preventDefault();
            args.stopPropagation();
            setTimeout(function () { close.focus() }, 0);
        });

        // Finds all focusable children
        var focusableElements = content.find(focusableElementsString);

        // Converts NodeList to Array
        focusableElements = Array.prototype.slice.call(focusableElements);
        firstTabStop = focusableElements[0];
        lastTabStop = focusableElements[focusableElements.length - 1];

        // Focus first
        firstTabStop.focus();
        return overlay;
    }
    /**
     * hideOverlay hide overlay
     * @memberOf module:overlay
     * @private
     */
    function hideOverlay(overlay) {
        $("#wrapper").removeClass("aria-hidden");
        var content = overlay.find(".overlay-inner");

        overlay.animate({ opacity: 0 },
            function () {
                overlay.hide();
                content.empty();

                if (mejs) {
                    for (var p in mejs.players) {
                        if ($("#" + mejs.players[p].id).parents(".overlay").length == 1) {
                            $("#" + mejs.players[p].id + ' video').attr('src', '');
                            mejs.players[p].remove();
                            mejs.players.splice(p, 1);
                        }
                    }
                }
            }
        );

        return overlay;
    }
    /**
     * createOverlay creates a markup for overlay and appends it to body 
     * @memberOf module:overlay
     * @private
     */
    function createOverlay() {
        var overlay = "<div class='overlay-wrapper'>" +
            "<div class='overlay component'>" +
            "<div class='component-content' role='dialog'>" +
            "<div class='overlay-inner'></div>" +
            "<div class='overlay-close' role='button' aria-label='Close dialog'></div>" +
            "</div>" +
            "</div>" +
            "</div>";

        $("body").append(overlay);
    }
    /**
    * initInstance method prevent event propagation and call 
    * ["preShowOverlay"]{@link module:overlay.preShowOverlay} and
    * ["loadOverlay"]{@link module:overlay.loadOverlay} methods
    * @memberOf module:overlay
    * @method
    * @param {jQuery} component Root DOM element of archive component wrapped by jQuery
    * @param {jQuery<DOMElement>} overlay overlay DOMElement
    * @param {jQuery<DOMElement>} overlayContent overlay content DOMElement 
    * @alias module:overlay.initInstance
    */
    api.initInstance = function (component, overlay, overlayContent) {
        component.on("click",
            function (event) {
                event.preventDefault();
                event.stopPropagation();
                preShowOverlay(overlay, overlayContent);

                var uri = this.href;
                loadOverlay(uri, overlay);

                var href = this.href.split('/');
                for (var i = 0; i <= href.length; i++) {
                    label = href.pop();
                    if (label.length == 0) {
                        continue;
                    }
                    break;
                }
            });
    }
    /**
    * init method makes preparation for overlay content, binds events
    * for proper work of overlay and
    * calls in a loop for each instance of overlay
    * [".initInstance"]{@link module:Accordion.initInstance} method.
    * @memberOf module:overlay
    * @alias module:overlay.init
    */
    api.init = function () {
        var overlayContent,
            overlaySource,
            overlayInner,
            overlayCloseLink,
            closeAction,
            page,
            overlay,
            content;

        if (!overlayPlaceholder && hasOverlayContent()) {
            createOverlay();
        }

        if (isOverlayPage()) {

            page = $(".overlay-page");
            overlayContent = page.children(".component-content");
            overlay = $("#spnOverlay");
            content = overlayContent.children(".overlay-inner");

            api.resizeOverlay(content, overlayContent, {
                width: overlayContent.data("width"),
                height: overlayContent.data("height"),
                percent: overlayContent.data("percent")
            });
            overlayContent.on("click", function (event) {
                event.stopPropagation();
            });
            page.on("click", function () {
                if (isPreviewMode()) {
                    location.href = location.href.replace("sc_mode=preview", "sc_mode=edit");
                }
            });
        }
        overlay = $(".overlay-wrapper > .overlay");
        overlayContent = overlay.find(".component-content");
        overlaySource = $(".overlay-source a:not(.initialized), a.overlay-source:not(.initialized)");
        overlayInner = $(".overlay-inner");
        overlayCloseLink = overlay.find(".overlay-close");

        closeAction = function () {
            hideOverlay(overlay);
            overlayInner.off("blur");
            setTimeout(function () {
                if (overlayClickSource != null) {
                    overlayClickSource.focus();
                }
            }, 0);
        };

        if (!overlayPlaceholder) {
            overlayContent.on("click", function (event) {
                event.stopPropagation();
            });

            overlay.on("click", function () {
                closeAction();
            });

            overlayCloseLink.on("click", function (args) {
                args.preventDefault();
                closeAction();
            });

            $("body").keydown(function (e) {
                // Check for TAB key press
                if (e.keyCode === 9) {
                    // SHIFT + TAB
                    if (e.shiftKey) {
                        if (document.activeElement === firstTabStop) {
                            e.preventDefault();
                            lastTabStop.focus();
                        }

                        // TAB
                    } else if (document.activeElement === lastTabStop) {
                        e.preventDefault();
                        // firstTabStop.attr('tabindex', 0);
                        firstTabStop.focus();
                    }
                }

                // ESCAPE
                if (e.keyCode === 27) {
                    e.stopPropagation();
                    closeAction();
                }
            });

            $(window).on("resize", function () {
                var height = $(window).height();
                height = height - marginTop - 0.1 * height;
                overlayContent.css("max-height", height + "px");
            });

            overlayPlaceholder = true;
        }


        overlaySource.each(function () {
            api.initInstance($(this), overlay, overlayContent);
            $(this).addClass("initialized");
        });


    };

    return api;

}(jQuery);

XA.register("overlay", XA.component.overlay);


[].forEach.call(document.querySelectorAll('.search-results.overlay-source'), function (e) {
    XA.dom.observeDOM(e, function () { XA.component.overlay.init(); });
});