diff src/DML/MainVisBundle/Resources/assets/marionette/modules/HelpModule.js @ 0:493bcb69166c

added public content
author Daniel Wolff
date Tue, 09 Feb 2016 20:54:02 +0100
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/DML/MainVisBundle/Resources/assets/marionette/modules/HelpModule.js	Tue Feb 09 20:54:02 2016 +0100
@@ -0,0 +1,418 @@
+"use strict";
+
+/*
+ * TODO
+ *  keep scroll position when resize
+ *
+ *  fix focus outline around links in TOC in FF
+ *      dotted border
+ */
+App.module("HelpModule", function(HelpModule, App, Backbone, Marionette, $, _, Logger) {
+
+    // Prevent auto start
+    HelpModule.startWithParent = false;
+
+    // Define options
+    var defaultModuleOptions = {
+            contentScrollDuration: 200,
+            resizeThrottleDuration: 200,
+            resizeDebounceDuration: 200,
+            scrollThrottleDuration: 200,
+            scrollDebounceDuration: 200,
+    };
+    var moduleOptions;
+
+    // Define private variables
+    var logger = null;
+    var $help = null;
+    var $helpBody = null;
+    var $helpCloser = null;
+    var $helpContentContainer = null;
+    var $helpContent = null;
+    var $helpContentHeaders = null;
+    var $helpTocContainer = null;
+    var $helpToc = null;
+
+    var contentScrollSavedPosition = null; // [offset, $materialHeader] $materialHeader - where to offset from
+    var resizing = false; // true when window is resizing, ignore
+    var scrolling = false; // true when content is being scrolled for whatever reason
+    var scrollingTo = null; // data id of an item that is being scrolled to during an animation
+
+    var lastShownMaterialId = null;
+    var pendingMaterialId = null;
+
+    var $lastContentHeader = null; // Not last used, but last in the list (to set up bottom margin)
+
+    // Initialization checker
+    var assertModuleIsInitialized = function() {
+        if (!$help) {
+            throw "HelpModule has not been initialized";
+        }
+    };
+
+    // Private functions
+    var updateContentBottomMargin = null;
+    var updateContentScrollSavedPosition = null;
+    var restoreContentScrollSavedPosition = null;
+
+    var updateTocCurrentItem = null;
+
+    /**
+     * Module initializer
+     *
+     */
+    HelpModule.addInitializer(function(options){
+
+        moduleOptions = _.extend(defaultModuleOptions, options);
+
+        logger = Logger.get("HelpModule");
+        //logger.setLevel(Logger.DEBUG);
+
+        // When window is resized, bottom margin of the content should be updated
+        updateContentBottomMargin = function(makeBigAndLockScroll) {
+            if (makeBigAndLockScroll) {
+                $helpContentContainer
+                    .css("overflow", "hidden");
+                $helpContent
+                    .css("border-bottom-width", 10000);
+            } else {
+                $helpContentContainer
+                    .css("overflow", "scroll");
+                $helpContent
+                    .css('border-bottom-width',
+                        Math.max(0,
+                                $helpContentContainer.outerHeight()
+                                - $helpContent.height()
+                                + $lastContentHeader.position().top
+                                - parseInt($helpContent.css('padding-bottom'), 10)
+                                + parseInt($lastContentHeader.css('margin-top'), 10)
+                        ));
+            }
+        };
+
+        // Looks at the current scroll position and updates
+        // contentScrollSavedPosition accordingly
+        updateContentScrollSavedPosition = function() {
+            var scrollTop = $helpContentContainer.scrollTop();
+            var $candidateHeader = $helpContentHeaders.first();
+
+            for (var i = 0; i <= $helpContentHeaders.length; i++) {
+                var $helpContentHeader = $($helpContentHeaders[i]);
+                if (!$helpContentHeader.length || $helpContentHeader.position().top >= scrollTop) {
+                    contentScrollSavedPosition = [
+                          Math.floor(scrollTop - $candidateHeader.position().top - parseInt($candidateHeader.css('margin-top')), 10),
+                          $candidateHeader
+                      ];
+                    updateTocCurrentItem();
+                    break;
+                } else {
+                    $candidateHeader = $helpContentHeader;
+                }
+            }
+
+            App.DataModule.Storage.setStrCache(HelpModule, "saved-scroll-position", contentScrollSavedPosition[0] + " " + contentScrollSavedPosition[1].attr('data-id'));
+        };
+
+        // scrolls to a saved scroll position
+        restoreContentScrollSavedPosition = function(animate) {
+            if (animate) {
+                scrollingTo = contentScrollSavedPosition[1].attr('data-id');
+            }
+            $helpContentContainer
+                .stop(true, false)
+                .scrollTo(contentScrollSavedPosition[1].position().top + parseInt(contentScrollSavedPosition[1].css('margin-top'), 10) + contentScrollSavedPosition[0], {
+                    duration: animate ? moduleOptions.contentScrollDuration : 0,
+                }, function() {
+                    scrollingTo = null;
+                });
+        };
+
+        updateTocCurrentItem = function() {
+            var newMaterialId = scrollingTo !== null ? scrollingTo : contentScrollSavedPosition[1].attr('data-id');
+
+            if (lastShownMaterialId !== newMaterialId) {
+                $helpToc
+                    .children()
+                    .removeClass("help__toc-element_current");
+                    //.setMod('help', 'toc-element', 'current', false);
+                $helpToc.find(_.str.sprintf("[data-id='%s']", newMaterialId))
+                    .addClass("help__toc-element_current");
+                    //.setMod('help', 'toc-element', 'current', true);
+            }
+
+            if (lastShownMaterialId != newMaterialId) {
+                if (HelpModule.isShowing()) {
+                    lastShownMaterialId = newMaterialId;
+                    HelpModule.trigger("show", {"materialId": newMaterialId});
+                }
+            }
+        };
+
+        $help = $.bem.generateBlock('help').setMod('help','state','hidden');
+        $helpBody = $.bem.generateElement('help', 'body');
+        $helpContentContainer = $.bem.generateElement('help', 'content-container');
+        $helpContent = $.bem.generateElement('help', 'content');
+        $helpTocContainer = $.bem.generateElement('help', 'toc-container');
+        $helpToc = $.bem.generateElement('help', 'toc');
+        $helpCloser = $.bem.generateElement('help', 'closer');
+
+        // Clicking outside help hides it
+        $help.click(function(event) {
+            if ($help.hasMod("help", "state_shown")) {
+                HelpModule.hide();
+            }
+            event.stopPropagation();
+        });
+        $helpBody.click(function(event) {
+            event.stopPropagation();
+        });
+
+        // Help content goes from #help-content template
+        // It is both an element and a block
+        $helpContent.addClass('help-content')
+            .append($(Backbone.Marionette.TemplateCache.get("#help-content")({
+                Ctrl: _.str.capitalize(App.keyboardMappings.ctrlTitle)
+            })));
+
+        // Help TOC (table of contents) is populated from headers in content
+        var usedDataIds = [];
+        $helpContentHeaders = $helpContent.find('h1, h2, h3');
+        $helpContentHeaders.each(function(i, helpContentHeader) {
+            var $helpContentHeader = $(helpContentHeader);
+            var title = $helpContentHeader.attr('data-toc');
+            if (!title) {
+                title = $helpContentHeader.text();
+            }
+            var id = $helpContentHeader.attr('data-id');
+            if (_.isUndefined(id)) {
+                id = _.str.slugify($helpContentHeader.text());
+            }
+            if (usedDataIds.indexOf(id) != -1) {
+                throw _.str.sprintf("There are more than one header with id = '%s' in help", id);
+            }
+            usedDataIds.push(id);
+            $helpContentHeader.attr('data-id', id);
+            var $currentHelpTocElement = $.bem.generateElement('a', 'help', 'toc-element')
+                .attr('href', id ? _.str.sprintf('#help/%s', id) : '#help')
+                .attr('data-id', id)
+                .setMod('help', 'toc-element', 'hierarchy', $helpContentHeader.prop('tagName').slice(1))
+                .text(title)
+                .click(function(event) {
+                    if ($.eventsugar.isAttemptToOpenInAnotherWindow(event)) {
+                        return;
+                    }
+                    event.preventDefault();
+                    HelpModule.show({materialId: id, forceScroll: true});
+                    return false;
+                });
+
+            $helpToc.append($currentHelpTocElement);
+            $lastContentHeader = $helpContentHeader;
+        });
+
+        // Restore scroll position from cache
+        var rawSavedScrollPosition = App.DataModule.Storage.getStrCache(HelpModule, "saved-scroll-position");
+        if (rawSavedScrollPosition) {
+            var i = rawSavedScrollPosition.indexOf(" ");
+            var offset = parseInt(rawSavedScrollPosition.slice(0,i));
+            var $materialHeader = $helpContentHeaders.filter(_.str.sprintf("[data-id='%s']", rawSavedScrollPosition.slice(i+1)));
+            if ($materialHeader.length) {
+                contentScrollSavedPosition = [offset, $materialHeader];
+            }
+        }
+        if (!contentScrollSavedPosition) {
+            contentScrollSavedPosition = [0, $helpContentHeaders.first()];
+        }
+        lastShownMaterialId = contentScrollSavedPosition[1].attr("data-id");
+
+        // Help closer
+        //// Move to the right for Windows
+        if (navigator && navigator.appVersion && navigator.appVersion.indexOf("Win") != -1) {
+            $helpCloser.setMod('help', 'closer', 'position', 'right');
+        }
+        //// Close help on click
+        $helpCloser.click(function(event) {
+            if ($help.hasMod("help", "state_shown")) {
+                HelpModule.hide();
+            }
+            event.stopPropagation();
+        });
+
+        // Build element hierarchy
+        $helpContentContainer.append($helpContent);
+        $helpTocContainer.append($helpToc);
+        $helpBody.append($helpContentContainer, $helpTocContainer);
+        $help.append($helpBody, $helpCloser);
+
+        $('.app__help').append($help);
+        $help.setMod("help", "animating", true);
+
+        // Window events
+        var $window = $(window);
+
+        $window.on("resize", function() {
+            if (!HelpModule.isShowing()) {
+                return;
+            }
+            if (!resizing) {
+                resizing = true;
+                updateContentBottomMargin(true);
+            }
+            restoreContentScrollSavedPosition();
+        });
+
+        $window.on("resize", _.debounce(
+                function(event) {
+                    if (!HelpModule.isShowing()) {
+                        return;
+                    }
+                    updateContentBottomMargin();
+                    resizing = false;
+                    restoreContentScrollSavedPosition(true);
+                },
+                moduleOptions.resizeDebounceDuration));
+
+        $helpContentContainer.on("scroll",_.throttle(
+              function(event){if (!resizing) {scrolling = true; updateContentScrollSavedPosition();}},
+              moduleOptions.scrollThrottleDuration, {trailing: false}));
+
+        $helpContentContainer.on("scroll", _.debounce(
+                function(event) {
+                    scrolling = false;
+                    updateContentScrollSavedPosition();
+                    if (HelpModule.isShowing()
+                            && pendingMaterialId !== null
+                            && pendingMaterialId != lastShownMaterialId
+                            && pendingMaterialId != contentScrollSavedPosition[1].attr('data-id')) {
+                        contentScrollSavedPosition = [0, $helpContentHeaders.filter(_.str.sprintf("[data-id='%s']", pendingMaterialId))];
+                        pendingMaterialId = null;
+                        restoreContentScrollSavedPosition(true);
+                    } else {
+                        pendingMaterialId = null;
+                    }
+                },
+                moduleOptions.scrollDebounceDuration));
+
+
+        /// Scroll fix for help
+        new ScrollFix($helpContentContainer.get(0));
+        $helpContentContainer.get(0).addEventListener('touchmove', function(event){
+            event.stopPropagation();
+        });
+
+        // Embedded content
+        var helpContentHasVimeo = false;
+        $helpContent.find("iframe").each(function() {
+            var $iframe = $(this);
+            var src = $iframe.attr("src");
+            if (!_.isString(src)) {
+                src = "";
+            }
+            if (src.indexOf("vimeo") !== -1) {
+                helpContentHasVimeo = true;
+                $(this).attr("data-type", "vimeo");
+            }
+        });
+        // enable control of vimeo
+        // see http://stackoverflow.com/questions/10401819/jquery-how-to-stop-vimeo-video-when-click
+        if (helpContentHasVimeo) {
+            var scriptElement = document.createElement('script');
+            scriptElement.type = 'text/javascript';
+            scriptElement.async = true;
+            scriptElement.src = "http://a.vimeocdn.com/js/froogaloop2.min.js";
+            document.getElementsByTagName('body')[0].appendChild(scriptElement);
+        }
+    });
+
+    /**
+     * Shows a specific material id (scrolls to it)
+     * or shows the top of the help page if no materialId is specified
+     *
+     * options
+     *      materialId
+     *      $dispatcher is a jquery element that triggered help page opening
+     *          this parameter helps animate the body of the help (TODO)
+     *
+     *      forceScroll
+     *      instant
+     *
+     */
+    HelpModule.show = function(options) {
+
+        var options = $.extend({}, options);
+        assertModuleIsInitialized();
+
+        var helpIsOpening = false;
+        if ($help.hasMod('help', 'state_hidden') || $help.hasMod('help', 'state_pre-hidden')) {
+            helpIsOpening = true;
+            $help.toggleClass('help_animating', !options.instant);
+            $help.setMod('help', 'state', 'pre-shown');
+            lastShownMaterialId = null;
+            pendingMaterialId = null;
+            updateContentBottomMargin();
+        }
+
+        var needToScroll = true;
+        var $materialHeader = $helpContent.find(_.str.sprintf("[data-id='%s']", options.materialId));
+        if ($materialHeader.length) {
+            pendingMaterialId = options.materialId;
+            if (!scrolling) {
+                if (options.forceScroll || !contentScrollSavedPosition[1].is($materialHeader)) {
+                    contentScrollSavedPosition = [0, $materialHeader];
+                } else {
+                    needToScroll = false;
+                }
+            }
+        }
+        if (helpIsOpening || needToScroll && (!scrolling || scrollingTo !== null)) {
+            restoreContentScrollSavedPosition(!helpIsOpening);
+            updateTocCurrentItem();
+        }
+
+        if (!needToScroll) {
+            scrollingTo = null;
+            pendingMaterialId = null;
+        }
+
+
+        if ($help.hasMod('help', 'state_pre-shown')) {
+            $help.setMod('help', 'state', 'shown');
+        }
+        if (!!options.instant) {
+            setTimeout(function() {
+                $help.setMod('help', 'animating', true);
+            }, 10)
+        }
+    };
+
+    /**
+     * Hides help
+     */
+    HelpModule.hide = function() {
+        assertModuleIsInitialized();
+
+        // disable content
+        //// pause vimeo
+        $helpContent.find("iframe[data-type=vimeo]").each(function() {
+            if (window.$f) {
+                $f(this).api("pause");
+            }
+        });
+
+        $help.setMod('help', 'state', 'pre-hidden');
+        HelpModule.trigger("hide");
+        setTimeout(function() {
+            if ($help.hasMod('help', 'state_pre-hidden')) {
+                $help.setMod('help', 'state', 'hidden');
+            };
+        }, 1000);
+    };
+
+    /**
+     * Returns true if help is being shown or is about to be shown
+     * False is return when help is not visible or is about to be hidden
+     */
+    HelpModule.isShowing = function() {
+        return $help.hasMod("help", "state_pre-shown") || $help.hasMod("help", "state_shown");
+    };
+}, Logger);