regulations/static/regulations/js/source/helpers.js
// Defines some globally useful helper functions
const $ = require('jquery');
const _ = require('underscore');
// indexOf polyfill
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf
// TODO this may make sense to move elsewhere
if (!Array.prototype.indexOf) {
/* eslint-disable no-extend-native,no-bitwise */
Array.prototype.indexOf = function indexOf(searchElement, initialIndex) {
let fromIndex = initialIndex;
if (this === undefined || this === null) {
throw new TypeError('"this" is null or not defined');
}
const length = this.length >>> 0; // Hack to convert object.length to a UInt32
fromIndex = +fromIndex || 0;
if (Math.abs(fromIndex) === Infinity) {
fromIndex = 0;
}
if (fromIndex < 0) {
fromIndex += length;
if (fromIndex < 0) {
fromIndex = 0;
}
}
for (;fromIndex < length; fromIndex += 1) {
if (this[fromIndex] === searchElement) {
return fromIndex;
}
}
return -1;
};
/* eslint-enable */
}
module.exports = {
// Finds parent-most reg paragraph
findBaseSection: function findBaseSection(id) {
let parts = [];
let interpIndex = -1;
if (id.indexOf('-') !== -1) {
parts = id.split('-');
} else {
return id;
}
// if what has been passed in is a base section already
// catches:
// 123
// 123-1
// 123-A
// 123-Interp
if (parts.length <= 2) {
return id;
}
interpIndex = parts.indexOf('Interp');
if (interpIndex !== -1) {
// catches 123-Interp-h1
if (parts[1] === 'Interp') {
return id;
}
// catches:
// 123-4-Interp
// 123-4-Interp-5
// 123-Subpart-Interp
// 123-Subpart-A-Interp
// 123-Subpart-Interp-4
// 123-Subpart-A-Interp-4
// 123-Appendices-Interp
// 123-Appendices-Interp-4
return parts.slice(0, interpIndex + 1).join('-');
}
// catches:
// 123-4-*
// 123-A-*
return `${parts[0]}-${parts[1]}`;
},
// Unpaired this function from the DOM to make
// it more testable and flexible. Look at resources.js
// to add places to look for version elements.
// To call: `findVersion(Resources.versionElements)`
// -- old message --
// these next two are a little desperate and heavy handed
// the next step, if the app were going to do more
// interesting things, is to introduce the concept of reg
// version and maybe effective dates to the architecture
// at that time, this should be removed
findVersion: function findVersion(versionElements) {
return $(versionElements.toc).attr('data-toc-version') ||
$(versionElements.regLandingPage).attr('data-base-version') ||
$(versionElements.timelineList).find('.stop-button').attr('data-version');
// includes .stop-button to be sure its not
// the comparison version in diff mode
},
// returns newer version. findVersion will return base version
findDiffVersion: function findDiffVersion(versionElements, current) {
const currentVersion = current || this.findVersion(versionElements);
let version;
version = $(versionElements.diffToc).attr('data-from-version');
if (!version || version === currentVersion) {
if ($(versionElements.timelineList).find('.version-link').attr('data-version') !== currentVersion) {
version = $(versionElements.timelineList).find('.version-link').attr('data-version');
}
}
return version;
},
isSupplement: function isSupplement(id) {
let parts;
if (typeof id !== 'undefined') {
parts = _.compact(id.split('-'));
if (parts.length < 2) {
return false;
}
if (parts[1].toLowerCase() === 'interp') {
return true;
}
}
return false;
},
isAppendix: function isAppendix(id) {
let parts;
if (typeof id !== 'undefined') {
parts = _.compact(id.split('-'));
if (parts.length < 2) {
return false;
}
if (isNaN(parts[1].substr(0, 1)) && parts[1].toLowerCase() !== 'interp') {
return true;
}
}
return false;
},
formatSubpartLabel: function formatSubpartLabel(id) {
// accepts 123-Subpart-C
const parts = id.split('-');
let label = 'Subpart ';
if (isNaN(parts[0]) === false && parts[1] === 'Subpart') {
label += parts[2];
}
return label;
},
// accepts to params:
// element to be expanded
// animation duration
toggleExpandable: function toggleExpandable($expandable, dur) {
$expandable.toggleClass('open')
.next('.chunk').slideToggle(dur);
},
/**
* Parse a preamble section ID.
* @param {string} id Preamble section ID
* @see unittests for example usage
*/
parsePreambleId: function parsePreambleId(id) {
const parts = id.split('-');
const docId = parts.shift();
const type = parts.shift();
let path = ['preamble', docId];
let section;
if (type === 'preamble') {
// Note: Document ID appears twice in preamble IDs
// TODO: Standardize IDs and drop the `slice`
path = path.concat(parts.slice(1, 2));
section = [docId].concat(parts.slice(1, 2));
} else if (type === 'cfr') {
// Sections for CFR changes include the two first parts; the ID
// 478-32-p243 maps to the section 478-32
path = path.concat(['cfr_changes', parts.slice(0, 2).join('-')]);
section = parts.slice(0, 2);
}
const parsed = {
path,
type,
docId,
section,
};
/**
* If linking to the top-level, we don't need a hash. Otherwise, link to
* the beginning of the associated subsection
**/
if (parts.length > 2) {
parsed.hash = parts.join('-');
}
return parsed;
},
/**
* Parse the citation link hash to open the correct section.
* @param {string} hash - the href link target section
* @param {string} type - expecting 'preamble-section' from preamble-view.js options.type
*
* Example internal citation link:
* <a href="#0000_0000-III-D-4" class="citation internal"
* data-section-id="0000_0000-III">III.D.4</a> take href link and
* create a string "0000_0000-preamble-0000_0000-III-D-4" to pass to
* 'section:open' event to load the linked section
*
* @see unittests
*/
parsePreambleCitationId: function parsePreambleCitationId(hash, type) {
// only grab the section info after #
const section = hash.substring(hash.indexOf('#') + 1);
const parts = section.split('-');
const docId = parts.shift();
const docType = type === 'preamble-section' ? 'preamble' : 'cfr';
return [docId, docType, section].join('-');
},
};