regulations/static/regulations/js/source/views/main/child-view.js
import { locationActiveEvt } from '../../redux/locationReduce';
import { activeParagraph } from '../../redux/reducers';
import storage from '../../redux/storage';
const $ = require('jquery');
const _ = require('underscore');
const Backbone = require('backbone');
require('../../events/scroll-stop.js');
const Router = require('../../router');
const HeaderEvents = require('../../events/header-events');
const MainEvents = require('../../events/main-events');
const GAEvents = require('../../events/ga-events');
Backbone.$ = $;
// Adjust the offset of how far past the section top before the wayfinder
// changes. Based on 2 * line-height + padding-top.
const WAYFINDER_SCROLL_OFFSET = window.WAYFINDER_SCROLL_OFFSET || 64;
const ChildView = Backbone.View.extend({
el: '#content-wrapper',
initialize: function initialize(options) {
this.options = options;
this.$topSection = this.$el.find('[data-page-type]');
this.sectionLabel = this.$topSection.data('label');
this.attachWayfinding();
if (this.options.render) {
if (!this.title) {
this.title = this.assembleTitle();
}
this.route(this.options);
GAEvents.trigger('section:open', this.options);
this.render();
} else if (this.options.id) {
MainEvents.trigger('section:sethandlers');
storage().dispatch(locationActiveEvt(this.options.id));
}
storage().dispatch(locationActiveEvt(this.options.id));
this.$activeSection = $(`#${this.options.id}`);
this.loadImages();
},
attachWayfinding: function attachWayfinding() {
this.updateWayfinding();
// * when a scroll event completes, check what the active secion is
// we can't scope the scroll to this.$el because there's no localized
// way to grab the scroll event, even with overflow:scroll
$(window).on('scrollstop', (_.bind(this.checkActiveSection, this)));
},
render: function render() {
this.updateWayfinding();
this.loadImages();
this.scroll();
HeaderEvents.trigger('section:open', this.sectionLabel);
storage().dispatch(locationActiveEvt(this.sectionLabel));
},
scroll: function scroll() {
let offsetTop;
let $scrollToId;
if (this.options.scrollToId) {
$scrollToId = $(`#${this.options.scrollToId}`);
if ($scrollToId.length) {
offsetTop = $scrollToId.offset().top;
}
window.scrollTo(0, offsetTop || 0);
}
},
changeFocus: function changeFocus(id) {
$(id).focus();
},
assembleTitle: function assembleTitle() {
const titleParts = _.compact(document.title.split(' '));
const newTitle = [titleParts[0], titleParts[1], this.sectionLabel, '|', 'eRegulations'];
return newTitle.join(' ');
},
// naive way to update the active table of contents link and wayfinding header
// once a scroll event ends, we loop through each content section DOM node
// the first one whose offset is greater than the window scroll position, accounting
// for the fixed position header (via margin/border offsets), is deemed the
// active section
checkActiveSection: function checkActiveSection() {
$.each(this.$sections, (idx, $section) => {
const previousSection = activeParagraph(storage()).paragraph;
if ($section.offset().top + WAYFINDER_SCROLL_OFFSET >= $(window).scrollTop()) {
if (_.isEmpty(previousSection) || (previousSection !== $section.id)) {
const currentSection = $section[0].id;
this.$activeSection = $($section[0]);
// **Event** trigger active section change
HeaderEvents.trigger('section:open', this.$activeSection.data('label'));
storage().dispatch(locationActiveEvt(currentSection));
if (typeof window.history !== 'undefined' && typeof window.history.replaceState !== 'undefined') {
// update hash in url
window.history.replaceState(
null,
null,
`${window.location.origin + window.location.pathname + window.location.search}#${currentSection}`,
);
}
return false;
}
}
return true;
});
},
updateWayfinding: function updateWayfinding() {
// cache all sections in the DOM eligible to be the active section
// also cache some jQobjs that we will refer to frequently.
//
// Sections that are eligible for being the active section.
this.$sections = this.$el
.find('li[id], .reg-section, .appendix-section, .supplement-section')
.map((idx, elm) => $(elm));
},
route: function route(options) {
if (Router.hasPushState && typeof options.noRoute === 'undefined') {
let url = this.url;
// if a hash has been passed in
if (options && options.scrollToId) {
url += `#${options.scrollToId}`;
this.navigate(url);
$('html, body').scrollTop($(`#${options.scrollToId}`).offset().top);
} else {
if (['diff', 'search-results'].indexOf(options.type) === -1) {
url += `#${options.id}`;
}
this.navigate(url);
}
}
},
navigate: function navigate(url) {
Router.navigate(url);
MainEvents.trigger('route', url);
document.title = this.title;
},
remove: function remove() {
$(window).off('scrollstop');
this.stopListening();
this.off();
return this;
},
// lazy load images as the user scrolls
loadImages: function loadImages() {
$('.reg-image').lazyload();
},
});
module.exports = ChildView;