Mercurial > hg > dml-open-vis
view src/DML/MainVisBundle/Resources/assets/marionette/modules/HelpModule.js @ 1:f38015048f48 tip
Added GPL
author | Daniel Wolff |
---|---|
date | Sat, 13 Feb 2016 20:43:38 +0100 |
parents | 493bcb69166c |
children |
line wrap: on
line source
"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);