@bigfishtv/cockpit
Version:
153 lines (129 loc) • 3.78 kB
JavaScript
/**
* 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()
}
}