diff options
Diffstat (limited to 'src/wwwroot/libraries/fomantic/dist/components/sticky.js')
| -rw-r--r-- | src/wwwroot/libraries/fomantic/dist/components/sticky.js | 957 |
1 files changed, 957 insertions, 0 deletions
diff --git a/src/wwwroot/libraries/fomantic/dist/components/sticky.js b/src/wwwroot/libraries/fomantic/dist/components/sticky.js new file mode 100644 index 0000000..561213f --- /dev/null +++ b/src/wwwroot/libraries/fomantic/dist/components/sticky.js @@ -0,0 +1,957 @@ +/*! + * # Fomantic-UI - Sticky + * http://github.com/fomantic/Fomantic-UI/ + * + * + * Released under the MIT license + * http://opensource.org/licenses/MIT + * + */ + +;(function ($, window, document, undefined) { + +'use strict'; + +$.isFunction = $.isFunction || function(obj) { + return typeof obj === "function" && typeof obj.nodeType !== "number"; +}; + +window = (typeof window != 'undefined' && window.Math == Math) + ? window + : (typeof self != 'undefined' && self.Math == Math) + ? self + : Function('return this')() +; + +$.fn.sticky = function(parameters) { + var + $allModules = $(this), + moduleSelector = $allModules.selector || '', + + time = new Date().getTime(), + performance = [], + + query = arguments[0], + methodInvoked = (typeof query == 'string'), + queryArguments = [].slice.call(arguments, 1), + returnedValue + ; + + $allModules + .each(function() { + var + settings = ( $.isPlainObject(parameters) ) + ? $.extend(true, {}, $.fn.sticky.settings, parameters) + : $.extend({}, $.fn.sticky.settings), + + className = settings.className, + namespace = settings.namespace, + error = settings.error, + + eventNamespace = '.' + namespace, + moduleNamespace = 'module-' + namespace, + + $module = $(this), + $window = $(window), + $scroll = $(settings.scrollContext), + $container, + $context, + + instance = $module.data(moduleNamespace), + + requestAnimationFrame = window.requestAnimationFrame + || window.mozRequestAnimationFrame + || window.webkitRequestAnimationFrame + || window.msRequestAnimationFrame + || function(callback) { setTimeout(callback, 0); }, + + element = this, + + documentObserver, + observer, + module + ; + + module = { + + initialize: function() { + + module.determineContainer(); + module.determineContext(); + module.verbose('Initializing sticky', settings, $container); + + module.save.positions(); + module.checkErrors(); + module.bind.events(); + + if(settings.observeChanges) { + module.observeChanges(); + } + module.instantiate(); + }, + + instantiate: function() { + module.verbose('Storing instance of module', module); + instance = module; + $module + .data(moduleNamespace, module) + ; + }, + + destroy: function() { + module.verbose('Destroying previous instance'); + module.reset(); + if(documentObserver) { + documentObserver.disconnect(); + } + if(observer) { + observer.disconnect(); + } + $window + .off('load' + eventNamespace, module.event.load) + .off('resize' + eventNamespace, module.event.resize) + ; + $scroll + .off('scrollchange' + eventNamespace, module.event.scrollchange) + ; + $module.removeData(moduleNamespace); + }, + + observeChanges: function() { + if('MutationObserver' in window) { + documentObserver = new MutationObserver(module.event.documentChanged); + observer = new MutationObserver(module.event.changed); + documentObserver.observe(document, { + childList : true, + subtree : true + }); + observer.observe(element, { + childList : true, + subtree : true + }); + observer.observe($context[0], { + childList : true, + subtree : true + }); + module.debug('Setting up mutation observer', observer); + } + }, + + determineContainer: function() { + if(settings.container) { + $container = $(settings.container); + } + else { + $container = $module.offsetParent(); + } + }, + + determineContext: function() { + if(settings.context) { + $context = $(settings.context); + } + else { + $context = $container; + } + if($context.length === 0) { + module.error(error.invalidContext, settings.context, $module); + return; + } + }, + + checkErrors: function() { + if( module.is.hidden() ) { + module.error(error.visible, $module); + } + if(module.cache.element.height > module.cache.context.height) { + module.reset(); + module.error(error.elementSize, $module); + return; + } + }, + + bind: { + events: function() { + $window + .on('load' + eventNamespace, module.event.load) + .on('resize' + eventNamespace, module.event.resize) + ; + // pub/sub pattern + $scroll + .off('scroll' + eventNamespace) + .on('scroll' + eventNamespace, module.event.scroll) + .on('scrollchange' + eventNamespace, module.event.scrollchange) + ; + } + }, + + event: { + changed: function(mutations) { + clearTimeout(module.timer); + module.timer = setTimeout(function() { + module.verbose('DOM tree modified, updating sticky menu', mutations); + module.refresh(); + }, 100); + }, + documentChanged: function(mutations) { + [].forEach.call(mutations, function(mutation) { + if(mutation.removedNodes) { + [].forEach.call(mutation.removedNodes, function(node) { + if(node == element || $(node).find(element).length > 0) { + module.debug('Element removed from DOM, tearing down events'); + module.destroy(); + } + }); + } + }); + }, + load: function() { + module.verbose('Page contents finished loading'); + requestAnimationFrame(module.refresh); + }, + resize: function() { + module.verbose('Window resized'); + requestAnimationFrame(module.refresh); + }, + scroll: function() { + requestAnimationFrame(function() { + $scroll.triggerHandler('scrollchange' + eventNamespace, $scroll.scrollTop() ); + }); + }, + scrollchange: function(event, scrollPosition) { + module.stick(scrollPosition); + settings.onScroll.call(element); + } + }, + + refresh: function(hardRefresh) { + module.reset(); + if(!settings.context) { + module.determineContext(); + } + if(hardRefresh) { + module.determineContainer(); + } + module.save.positions(); + module.stick(); + settings.onReposition.call(element); + }, + + supports: { + sticky: function() { + var + $element = $('<div/>') + ; + $element.addClass(className.supported); + return($element.css('position').match('sticky')); + } + }, + + save: { + lastScroll: function(scroll) { + module.lastScroll = scroll; + }, + elementScroll: function(scroll) { + module.elementScroll = scroll; + }, + positions: function() { + var + scrollContext = { + height : $scroll.height() + }, + element = { + margin: { + top : parseInt($module.css('margin-top'), 10), + bottom : parseInt($module.css('margin-bottom'), 10), + }, + offset : $module.offset(), + width : $module.outerWidth(), + height : $module.outerHeight() + }, + context = { + offset : $context.offset(), + height : $context.outerHeight() + } + ; + if( !module.is.standardScroll() ) { + module.debug('Non-standard scroll. Removing scroll offset from element offset'); + + scrollContext.top = $scroll.scrollTop(); + scrollContext.left = $scroll.scrollLeft(); + + element.offset.top += scrollContext.top; + context.offset.top += scrollContext.top; + element.offset.left += scrollContext.left; + context.offset.left += scrollContext.left; + } + module.cache = { + fits : ( (element.height + settings.offset) <= scrollContext.height), + sameHeight : (element.height == context.height), + scrollContext : { + height : scrollContext.height + }, + element: { + margin : element.margin, + top : element.offset.top - element.margin.top, + left : element.offset.left, + width : element.width, + height : element.height, + bottom : element.offset.top + element.height + }, + context: { + top : context.offset.top, + height : context.height, + bottom : context.offset.top + context.height + } + }; + module.set.containerSize(); + + module.stick(); + module.debug('Caching element positions', module.cache); + } + }, + + get: { + direction: function(scroll) { + var + direction = 'down' + ; + scroll = scroll || $scroll.scrollTop(); + if(module.lastScroll !== undefined) { + if(module.lastScroll < scroll) { + direction = 'down'; + } + else if(module.lastScroll > scroll) { + direction = 'up'; + } + } + return direction; + }, + scrollChange: function(scroll) { + scroll = scroll || $scroll.scrollTop(); + return (module.lastScroll) + ? (scroll - module.lastScroll) + : 0 + ; + }, + currentElementScroll: function() { + if(module.elementScroll) { + return module.elementScroll; + } + return ( module.is.top() ) + ? Math.abs(parseInt($module.css('top'), 10)) || 0 + : Math.abs(parseInt($module.css('bottom'), 10)) || 0 + ; + }, + + elementScroll: function(scroll) { + scroll = scroll || $scroll.scrollTop(); + var + element = module.cache.element, + scrollContext = module.cache.scrollContext, + delta = module.get.scrollChange(scroll), + maxScroll = (element.height - scrollContext.height + settings.offset), + elementScroll = module.get.currentElementScroll(), + possibleScroll = (elementScroll + delta) + ; + if(module.cache.fits || possibleScroll < 0) { + elementScroll = 0; + } + else if(possibleScroll > maxScroll ) { + elementScroll = maxScroll; + } + else { + elementScroll = possibleScroll; + } + return elementScroll; + } + }, + + remove: { + lastScroll: function() { + delete module.lastScroll; + }, + elementScroll: function(scroll) { + delete module.elementScroll; + }, + minimumSize: function() { + $container + .css('min-height', '') + ; + }, + offset: function() { + $module.css('margin-top', ''); + } + }, + + set: { + offset: function() { + module.verbose('Setting offset on element', settings.offset); + $module + .css('margin-top', settings.offset) + ; + }, + containerSize: function() { + var + tagName = $container.get(0).tagName + ; + if(tagName === 'HTML' || tagName == 'body') { + // this can trigger for too many reasons + //module.error(error.container, tagName, $module); + module.determineContainer(); + } + else { + if( Math.abs($container.outerHeight() - module.cache.context.height) > settings.jitter) { + module.debug('Context has padding, specifying exact height for container', module.cache.context.height); + $container.css({ + height: module.cache.context.height + }); + } + } + }, + minimumSize: function() { + var + element = module.cache.element + ; + $container + .css('min-height', element.height) + ; + }, + scroll: function(scroll) { + module.debug('Setting scroll on element', scroll); + if(module.elementScroll == scroll) { + return; + } + if( module.is.top() ) { + $module + .css('bottom', '') + .css('top', -scroll) + ; + } + if( module.is.bottom() ) { + $module + .css('top', '') + .css('bottom', scroll) + ; + } + }, + size: function() { + if(module.cache.element.height !== 0 && module.cache.element.width !== 0) { + element.style.setProperty('width', module.cache.element.width + 'px', 'important'); + element.style.setProperty('height', module.cache.element.height + 'px', 'important'); + } + } + }, + + is: { + standardScroll: function() { + return ($scroll[0] == window); + }, + top: function() { + return $module.hasClass(className.top); + }, + bottom: function() { + return $module.hasClass(className.bottom); + }, + initialPosition: function() { + return (!module.is.fixed() && !module.is.bound()); + }, + hidden: function() { + return (!$module.is(':visible')); + }, + bound: function() { + return $module.hasClass(className.bound); + }, + fixed: function() { + return $module.hasClass(className.fixed); + } + }, + + stick: function(scroll) { + var + cachedPosition = scroll || $scroll.scrollTop(), + cache = module.cache, + fits = cache.fits, + sameHeight = cache.sameHeight, + element = cache.element, + scrollContext = cache.scrollContext, + context = cache.context, + offset = (module.is.bottom() && settings.pushing) + ? settings.bottomOffset + : settings.offset, + scroll = { + top : cachedPosition + offset, + bottom : cachedPosition + offset + scrollContext.height + }, + elementScroll = (fits) + ? 0 + : module.get.elementScroll(scroll.top), + + // shorthand + doesntFit = !fits, + elementVisible = (element.height !== 0) + ; + if(elementVisible && !sameHeight) { + + if( module.is.initialPosition() ) { + if(scroll.top >= context.bottom) { + module.debug('Initial element position is bottom of container'); + module.bindBottom(); + } + else if(scroll.top > element.top) { + if( (element.height + scroll.top - elementScroll) >= context.bottom ) { + module.debug('Initial element position is bottom of container'); + module.bindBottom(); + } + else { + module.debug('Initial element position is fixed'); + module.fixTop(); + } + } + + } + else if( module.is.fixed() ) { + + // currently fixed top + if( module.is.top() ) { + if( scroll.top <= element.top ) { + module.debug('Fixed element reached top of container'); + module.setInitialPosition(); + } + else if( (element.height + scroll.top - elementScroll) >= context.bottom ) { + module.debug('Fixed element reached bottom of container'); + module.bindBottom(); + } + // scroll element if larger than screen + else if(doesntFit) { + module.set.scroll(elementScroll); + module.save.lastScroll(scroll.top); + module.save.elementScroll(elementScroll); + } + } + + // currently fixed bottom + else if(module.is.bottom() ) { + + // top edge + if( (scroll.bottom - element.height) <= element.top) { + module.debug('Bottom fixed rail has reached top of container'); + module.setInitialPosition(); + } + // bottom edge + else if(scroll.bottom >= context.bottom) { + module.debug('Bottom fixed rail has reached bottom of container'); + module.bindBottom(); + } + // scroll element if larger than screen + else if(doesntFit) { + module.set.scroll(elementScroll); + module.save.lastScroll(scroll.top); + module.save.elementScroll(elementScroll); + } + + } + } + else if( module.is.bottom() ) { + if( scroll.top <= element.top ) { + module.debug('Jumped from bottom fixed to top fixed, most likely used home/end button'); + module.setInitialPosition(); + } + else { + if(settings.pushing) { + if(module.is.bound() && scroll.bottom <= context.bottom ) { + module.debug('Fixing bottom attached element to bottom of browser.'); + module.fixBottom(); + } + } + else { + if(module.is.bound() && (scroll.top <= context.bottom - element.height) ) { + module.debug('Fixing bottom attached element to top of browser.'); + module.fixTop(); + } + } + } + } + } + }, + + bindTop: function() { + module.debug('Binding element to top of parent container'); + module.remove.offset(); + $module + .css({ + left : '', + top : '', + marginBottom : '' + }) + .removeClass(className.fixed) + .removeClass(className.bottom) + .addClass(className.bound) + .addClass(className.top) + ; + settings.onTop.call(element); + settings.onUnstick.call(element); + }, + bindBottom: function() { + module.debug('Binding element to bottom of parent container'); + module.remove.offset(); + $module + .css({ + left : '', + top : '' + }) + .removeClass(className.fixed) + .removeClass(className.top) + .addClass(className.bound) + .addClass(className.bottom) + ; + settings.onBottom.call(element); + settings.onUnstick.call(element); + }, + + setInitialPosition: function() { + module.debug('Returning to initial position'); + module.unfix(); + module.unbind(); + }, + + + fixTop: function() { + module.debug('Fixing element to top of page'); + if(settings.setSize) { + module.set.size(); + } + module.set.minimumSize(); + module.set.offset(); + $module + .css({ + left : module.cache.element.left, + bottom : '', + marginBottom : '' + }) + .removeClass(className.bound) + .removeClass(className.bottom) + .addClass(className.fixed) + .addClass(className.top) + ; + settings.onStick.call(element); + }, + + fixBottom: function() { + module.debug('Sticking element to bottom of page'); + if(settings.setSize) { + module.set.size(); + } + module.set.minimumSize(); + module.set.offset(); + $module + .css({ + left : module.cache.element.left, + bottom : '', + marginBottom : '' + }) + .removeClass(className.bound) + .removeClass(className.top) + .addClass(className.fixed) + .addClass(className.bottom) + ; + settings.onStick.call(element); + }, + + unbind: function() { + if( module.is.bound() ) { + module.debug('Removing container bound position on element'); + module.remove.offset(); + $module + .removeClass(className.bound) + .removeClass(className.top) + .removeClass(className.bottom) + ; + } + }, + + unfix: function() { + if( module.is.fixed() ) { + module.debug('Removing fixed position on element'); + module.remove.minimumSize(); + module.remove.offset(); + $module + .removeClass(className.fixed) + .removeClass(className.top) + .removeClass(className.bottom) + ; + settings.onUnstick.call(element); + } + }, + + reset: function() { + module.debug('Resetting elements position'); + module.unbind(); + module.unfix(); + module.resetCSS(); + module.remove.offset(); + module.remove.lastScroll(); + }, + + resetCSS: function() { + $module + .css({ + width : '', + height : '' + }) + ; + $container + .css({ + height: '' + }) + ; + }, + + setting: function(name, value) { + if( $.isPlainObject(name) ) { + $.extend(true, settings, name); + } + else if(value !== undefined) { + settings[name] = value; + } + else { + return settings[name]; + } + }, + internal: function(name, value) { + if( $.isPlainObject(name) ) { + $.extend(true, module, name); + } + else if(value !== undefined) { + module[name] = value; + } + else { + return module[name]; + } + }, + debug: function() { + if(!settings.silent && settings.debug) { + if(settings.performance) { + module.performance.log(arguments); + } + else { + module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); + module.debug.apply(console, arguments); + } + } + }, + verbose: function() { + if(!settings.silent && settings.verbose && settings.debug) { + if(settings.performance) { + module.performance.log(arguments); + } + else { + module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); + module.verbose.apply(console, arguments); + } + } + }, + error: function() { + if(!settings.silent) { + module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); + module.error.apply(console, arguments); + } + }, + performance: { + log: function(message) { + var + currentTime, + executionTime, + previousTime + ; + if(settings.performance) { + currentTime = new Date().getTime(); + previousTime = time || currentTime; + executionTime = currentTime - previousTime; + time = currentTime; + performance.push({ + 'Name' : message[0], + 'Arguments' : [].slice.call(message, 1) || '', + 'Element' : element, + 'Execution Time' : executionTime + }); + } + clearTimeout(module.performance.timer); + module.performance.timer = setTimeout(module.performance.display, 0); + }, + display: function() { + var + title = settings.name + ':', + totalTime = 0 + ; + time = false; + clearTimeout(module.performance.timer); + $.each(performance, function(index, data) { + totalTime += data['Execution Time']; + }); + title += ' ' + totalTime + 'ms'; + if(moduleSelector) { + title += ' \'' + moduleSelector + '\''; + } + if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { + console.groupCollapsed(title); + if(console.table) { + console.table(performance); + } + else { + $.each(performance, function(index, data) { + console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); + }); + } + console.groupEnd(); + } + performance = []; + } + }, + invoke: function(query, passedArguments, context) { + var + object = instance, + maxDepth, + found, + response + ; + passedArguments = passedArguments || queryArguments; + context = element || context; + if(typeof query == 'string' && object !== undefined) { + query = query.split(/[\. ]/); + maxDepth = query.length - 1; + $.each(query, function(depth, value) { + var camelCaseValue = (depth != maxDepth) + ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) + : query + ; + if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { + object = object[camelCaseValue]; + } + else if( object[camelCaseValue] !== undefined ) { + found = object[camelCaseValue]; + return false; + } + else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { + object = object[value]; + } + else if( object[value] !== undefined ) { + found = object[value]; + return false; + } + else { + return false; + } + }); + } + if ( $.isFunction( found ) ) { + response = found.apply(context, passedArguments); + } + else if(found !== undefined) { + response = found; + } + if(Array.isArray(returnedValue)) { + returnedValue.push(response); + } + else if(returnedValue !== undefined) { + returnedValue = [returnedValue, response]; + } + else if(response !== undefined) { + returnedValue = response; + } + return found; + } + }; + + if(methodInvoked) { + if(instance === undefined) { + module.initialize(); + } + module.invoke(query); + } + else { + if(instance !== undefined) { + instance.invoke('destroy'); + } + module.initialize(); + } + }) + ; + + return (returnedValue !== undefined) + ? returnedValue + : this + ; +}; + +$.fn.sticky.settings = { + + name : 'Sticky', + namespace : 'sticky', + + silent : false, + debug : false, + verbose : true, + performance : true, + + // whether to stick in the opposite direction on scroll up + pushing : false, + + context : false, + container : false, + + // Context to watch scroll events + scrollContext : window, + + // Offset to adjust scroll + offset : 0, + + // Offset to adjust scroll when attached to bottom of screen + bottomOffset : 0, + + // will only set container height if difference between context and container is larger than this number + jitter : 5, + + // set width of sticky element when it is fixed to page (used to make sure 100% width is maintained if no fixed size set) + setSize : true, + + // Whether to automatically observe changes with Mutation Observers + observeChanges : false, + + // Called when position is recalculated + onReposition : function(){}, + + // Called on each scroll + onScroll : function(){}, + + // Called when element is stuck to viewport + onStick : function(){}, + + // Called when element is unstuck from viewport + onUnstick : function(){}, + + // Called when element reaches top of context + onTop : function(){}, + + // Called when element reaches bottom of context + onBottom : function(){}, + + error : { + container : 'Sticky element must be inside a relative container', + visible : 'Element is hidden, you must call refresh after element becomes visible. Use silent setting to surpress this warning in production.', + method : 'The method you called is not defined.', + invalidContext : 'Context specified does not exist', + elementSize : 'Sticky element is larger than its container, cannot create sticky.' + }, + + className : { + bound : 'bound', + fixed : 'fixed', + supported : 'native', + top : 'top', + bottom : 'bottom' + } + +}; + +})( jQuery, window, document ); |
