UNPKG

@bigfishtv/cockpit

Version:

153 lines (129 loc) 3.78 kB
/** * BreadcrumbHandler * @module Components/BreadCrumbHandler */ import $ from 'jquery' import throttle from 'lodash/throttle' const headerBuffer = -5 // px, appromixate size before title is shown const scrollThrottle = 150 const $window = $(window) function animateScroll(position, time = 500, ease = 'linear', callback = () => {}) { $('html, body').animate({ scrollTop: position }, time, ease, callback) } /** * Breadcrumb handler class, uses jquery to look at nested breadcrumb data tags and dispatches updates all subscribed callbacks. * Typically, one instance of this class is created in the global scope so it can be referenced easily */ export default class BreadcrumbHandler { constructor(config = {}) { window.breadcrumbs = { addCallback: this.addCallback.bind(this), removeCallback: this.removeCallback.bind(this), scrollTo: this.scrollTo.bind(this), update: this.update.bind(this), } this.breadcrumbs = [] this.callbacks = [] this.currentElement = null this.headerHeight = config.headerHeight || 90 $window.scroll(throttle(this.handleScroll.bind(this), scrollThrottle)) } /** * Called by something that wants to subscribe to updates * @param {Function} callback */ addCallback(callback) { this.callbacks.push(callback) } /** * Called by something that wants to unsubscribe from updates * @param {Function} callback */ removeCallback(callback) { this.callbacks = this.callbacks.filter(cb => cb !== callback) } /** * Called when a new breadcrumb wrapper comes into view, dispatches updates to all subscribed callbacks * @param {jQueryDOM} elem The element currently in view */ newFocus(elem) { if (elem == this.currentElement) return else this.currentElement = elem const trail = this.getCurrentTrail(elem) for (let callback of this.callbacks) { callback(trail) } } /** * Gets * @param {jQueryDOM} elem The element to get the breadcrumb trail from * @return {Object[]} Returns a trail as an array of objects containg title url and elem */ getCurrentTrail(elem) { if (!elem) { return [] } const $elem = $(elem) const trail = [ { title: $elem.attr('data-breadcrumb') || 'Untitled', url: $elem.attr('data-breadcrumb-url'), elem: elem, }, ] $elem.parents('[data-breadcrumb]').each((i, crumb) => { let $crumb = $(crumb) trail.push({ title: $crumb.attr('data-breadcrumb') || 'Untitled', url: $crumb.attr('data-breadcrumb-url'), elem: crumb, }) }) trail.reverse() return trail } /** * Called on breadcrumb trail segment click, smooth scrolls to relevant section * @param {jQueryDOM} breadcrumb element to scroll to */ scrollTo(breadcrumb) { const scrollTop = $window.scrollTop() const offsetY = $(breadcrumb).offset().top - this.headerHeight - headerBuffer - 1 if (scrollTop == offsetY) return $('[data-breadcrumb-focus]').removeAttr('data-breadcrumb-focus') animateScroll(offsetY, 500, 'swing', () => { if (!breadcrumb.active) $(breadcrumb).attr('data-breadcrumb-focus', '') }) } update() { this.currentElement = null this.handleScroll() } /** * Called on (throttled) scroll change, gets most visible breadcrumb */ handleScroll() { let currentElement = null const threshold = $window.scrollTop() + this.headerHeight - headerBuffer $( $('[data-breadcrumb]') .get() .reverse() ).each((i, crumb) => { const $crumb = $(crumb) const top = $crumb.offset().top const bottom = top + $crumb.height() if (threshold >= top && threshold < bottom) { currentElement = crumb return false } }) this.newFocus(currentElement) } /** * Inits breadcrumb handler by calling handleScroll */ init() { this.handleScroll() } }