| 'use strict'; |
| |
| var _fullThrottle = require('../utils/full-throttle'); |
| |
| var _fullThrottle2 = _interopRequireDefault(_fullThrottle); |
| |
| var _jsonUtils = require('../utils/json-utils'); |
| |
| var _constants = require('../utils/constants'); |
| |
| function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } |
| |
| (function () { |
| 'use strict'; |
| |
| var MDL_LAYOUT_CONTENT = 'mdl-layout__content'; |
| var IS_SCROLL_CLASS = 'mdlext-is-scroll'; |
| |
| /** |
| * @constructor |
| * @param {Element} element The element that will be upgraded. |
| */ |
| var MaterialExtStickyHeader = function MaterialExtStickyHeader(element) { |
| // Stores the element. |
| this.header_ = element; |
| |
| // Heder listens to scroll events from content |
| this.content_ = null; |
| this.lastScrollTop_ = 0; |
| |
| // Default config |
| this.config_ = { |
| visibleAtScrollEnd: false |
| }; |
| |
| this.mutationObserver_ = null; |
| |
| this.drawing_ = false; |
| |
| // Initialize instance. |
| this.init(); |
| }; |
| |
| window['MaterialExtStickyHeader'] = MaterialExtStickyHeader; |
| |
| /** |
| * Update header width |
| * @private |
| */ |
| MaterialExtStickyHeader.prototype.recalcWidth_ = function () { |
| this.header_.style.width = this.content_.clientWidth + 'px'; |
| }; |
| |
| var throttleResize = (0, _fullThrottle2.default)(function (self) { |
| return self.recalcWidth_(); |
| }); |
| |
| /** |
| * Adjust header width when window resizes or oreientation changes |
| * @param event |
| * @private |
| */ |
| MaterialExtStickyHeader.prototype.resizeHandler_ = function () /* event */{ |
| throttleResize(this); |
| }; |
| |
| /** |
| * Update header position |
| * @private |
| */ |
| MaterialExtStickyHeader.prototype.reposition_ = function () { |
| |
| var currentContentScrollTop = this.content_.scrollTop; |
| var scrollDiff = this.lastScrollTop_ - currentContentScrollTop; |
| |
| if (currentContentScrollTop <= 0) { |
| // Scrolled to the top. Header sticks to the top |
| this.header_.style.top = '0'; |
| this.header_.classList.remove(IS_SCROLL_CLASS); |
| } else if (scrollDiff > 0) { |
| |
| if (scrollDiff >= this.header_.offsetHeight) { |
| |
| // Scrolled up. Header slides in |
| var headerTop = parseInt(window.getComputedStyle(this.header_).getPropertyValue('top')) || 0; |
| if (headerTop != 0) { |
| this.header_.style.top = '0'; |
| this.header_.classList.add(IS_SCROLL_CLASS); |
| } |
| this.lastScrollTop_ = currentContentScrollTop; |
| } |
| return; |
| } else if (scrollDiff < 0) { |
| // Scrolled down |
| this.header_.classList.add(IS_SCROLL_CLASS); |
| var _headerTop = parseInt(window.getComputedStyle(this.header_).getPropertyValue('top')) || 0; |
| |
| if (this.content_.scrollHeight - this.content_.scrollTop <= this.content_.offsetHeight) { |
| // Bottom of content |
| if (_headerTop != 0) { |
| this.header_.style.top = this.config_.visibleAtScrollEnd ? '0' : '-' + this.header_.offsetHeight + 'px'; |
| } |
| } else { |
| _headerTop += scrollDiff; |
| var offsetHeight = this.header_.offsetHeight; |
| this.header_.style.top = (Math.abs(_headerTop) > offsetHeight ? -offsetHeight : _headerTop) + 'px'; |
| } |
| } |
| |
| this.lastScrollTop_ = currentContentScrollTop; |
| }; |
| |
| var throttleScroll = (0, _fullThrottle2.default)(function (self) { |
| return self.reposition_(); |
| }); |
| |
| /** |
| * Scroll header when content scrolls |
| * @param event |
| * @private |
| */ |
| MaterialExtStickyHeader.prototype.scrollHandler_ = function () /* event */{ |
| throttleScroll(this); |
| }; |
| |
| /** |
| * Init header position |
| * @private |
| */ |
| MaterialExtStickyHeader.prototype.updatePosition_ = function () /* event */{ |
| this.recalcWidth_(); |
| this.reposition_(); |
| }; |
| |
| /** |
| * Add mutation observer |
| * @private |
| */ |
| MaterialExtStickyHeader.prototype.addMutationObserver_ = function () { |
| var _this = this; |
| |
| // jsdom does not support MutationObserver - so this is not testable |
| /* istanbul ignore next */ |
| this.mutationObserver_ = new MutationObserver(function () /*mutations*/{ |
| // Adjust header width if content changes (e.g. in a SPA) |
| _this.updatePosition_(); |
| }); |
| |
| this.mutationObserver_.observe(this.content_, { |
| attributes: false, |
| childList: true, |
| characterData: false, |
| subtree: true |
| }); |
| }; |
| |
| /** |
| * Removes event listeners |
| * @private |
| */ |
| MaterialExtStickyHeader.prototype.removeListeners_ = function () { |
| |
| window.removeEventListener('resize', this.resizeHandler_); |
| window.removeEventListener('orientationchange', this.resizeHandler_); |
| |
| if (this.content_) { |
| this.content_.removeEventListener('scroll', this.scrollHandler_); |
| } |
| |
| if (this.mutationObserver_) { |
| this.mutationObserver_.disconnect(); |
| this.mutationObserver_ = null; |
| } |
| }; |
| |
| /** |
| * Initialize component |
| */ |
| MaterialExtStickyHeader.prototype.init = function () { |
| |
| if (this.header_) { |
| |
| this.removeListeners_(); |
| |
| if (this.header_.hasAttribute('data-config')) { |
| this.config_ = (0, _jsonUtils.jsonStringToObject)(this.header_.getAttribute('data-config')); |
| } |
| |
| this.content_ = this.header_.parentNode.querySelector('.' + MDL_LAYOUT_CONTENT) || null; |
| |
| if (this.content_) { |
| this.content_.style.paddingTop = this.header_.offsetHeight + 'px'; // Make room for sticky header |
| this.lastScrollTop_ = this.content_.scrollTop; |
| |
| this.content_.addEventListener('scroll', this.scrollHandler_.bind(this)); |
| window.addEventListener('resize', this.resizeHandler_.bind(this)); |
| window.addEventListener('orientationchange', this.resizeHandler_.bind(this)); |
| |
| this.addMutationObserver_(); |
| this.updatePosition_(); |
| |
| // Set upgraded flag |
| this.header_.classList.add(_constants.IS_UPGRADED); |
| } |
| } |
| }; |
| |
| /* |
| * Downgrade component |
| * E.g remove listeners and clean up resources |
| * |
| * Nothing to clean |
| * |
| MaterialExtStickyHeader.prototype.mdlDowngrade_ = function() { |
| 'use strict'; |
| console.log('***** MaterialExtStickyHeader.prototype.mdlDowngrade_'); |
| }; |
| */ |
| |
| // The component registers itself. It can assume componentHandler is available |
| // in the global scope. |
| /* eslint no-undef: 0 */ |
| componentHandler.register({ |
| constructor: MaterialExtStickyHeader, |
| classAsString: 'MaterialExtStickyHeader', |
| cssClass: 'mdlext-js-sticky-header' |
| }); |
| })(); /** |
| * @license |
| * Copyright 2016 Leif Olsen. All Rights Reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| * This code is built with Google Material Design Lite, |
| * which is Licensed under the Apache License, Version 2.0 |
| */ |
| |
| /** |
| * A sticky header makes site navigation easily accessible anywhere on the page and saves content space at the same. |
| * The header should auto-hide, i.e. hiding the header automatically when a user starts scrolling down the page and |
| * bringing the header back when a user might need it: they reach the bottom of the page or start scrolling up. |
| */ |