File: development/modules/anchor/js/anchor.js
import $ from 'jquery';
import {last} from '../../../js/utils/array';
import DOMModule from '../../__base/dommodule';
/**
* @module Modules
*/
const DEFAULT_CONTEXT = 'html, body';
const DEFAULT_OFFSET = 0;
const DEFAULT_DURATION = 300;
const DEFAULT_URL_EXCLUSION = true;
const AnchorTriggerStates = [
{name: 'init', events: {render: 'rendered'}},
{name: 'rendered', events: {start: 'running'}},
{name: 'running', events: {}}
];
// PRIVATE METHODS
const handleClick = Symbol('_handleClick');
/**
* AnchorTrigger module
* @class Modules.AnchorTrigger
* @constructor
* @extends Modules._DOMModule
*/
class AnchorTrigger extends DOMModule {
constructor (options) {
super(options);
this.addStates(AnchorTriggerStates);
this._offset = $(this.$data('excluded-height')).outerHeight() || DEFAULT_OFFSET;
this._duration = this.$data('duration') || DEFAULT_DURATION;
this._hideFromURL = this.$data('anchor-url') || DEFAULT_URL_EXCLUSION;
this._reference = last(this.$rootNode[0].href.split('#'));
this.$contextNode = $(this.$data('context') || DEFAULT_CONTEXT);
this.$referenceNode = $('#' + this._reference);
}
/**
* Creates a new AnchorTrigger instance without the "new" keyword but does not
* initialize it
* @method of
* @static
* @for Modules.AnchorTrigger
* @param {node|string|configs} node Node, CSS selector or configuration object
* @return {AnchorTrigger} A new instance
*
* @example
* import AnchorTrigger from './modules/anchor/js/anchor';
*
* const myTrigger = AnchorTrigger.of('[data-widget=AnchorTrigger]');
* myTrigger.render().startUp();
*/
static of (node) {
return new AnchorTrigger(node);
}
/**
* Finds and initializes all Accordions on a page at once
* @method findAll
* @static
* @for Modules.AnchorTrigger
* @param {string} selector CSS selector to match nodes against
* @return {array} A list of instances
*
* @example
* import AnchorTrigger from './modules/anchor/js/anchor';
*
* AnchorTrigger.findAll('[data-widget=AnchorTrigger]');
*/
static findAll(selector) {
return $(selector).toArray().map((node) => {
const trigger = AnchorTrigger.of(node);
trigger.render().startUp();
return trigger;
});
}
/**
* Renders the instance and checks if a matching reference node has been found
* @method render
* @for Modules.AnchorTrigger
* @return {this} The instance
*
* @example
* import AnchorTrigger from './modules/anchor/js/anchor';
*
* const myTrigger = AnchorTrigger.of('[data-widget=AnchorTrigger]');
* myTrigger.render();
*/
render() {
if (this.getState() === 'init') {
this.consume('render');
if (!this.$referenceNode.length) {
throw `AnchorTrigger: No matching anchor found: #${this._reference}`;
}
}
return this;
}
/**
* Attaches all eventlisteners to the instance
* @method startUp
* @for Modules.AnchorTrigger
* @return {undefined} Nothing
*
* @example
* import AnchorTrigger from './modules/anchor/js/anchor';
*
* const myTrigger = AnchorTrigger.of('[data-widget=AnchorTrigger]');
* myTrigger.render().startUp();
*/
startUp() {
if (this.getState() === 'rendered') {
this.consume('start');
this.onClick(handleClick);
}
}
// === PRIVATE STUFF ===
/**
* Updates the hash part of the current URL of the browser
* @method _updateHash
* @for Modules.AnchorTrigger
* @private
* @param {string} hash The new location.hash value
* @return {undefined} Nothing
*/
_updateHash (_hash) {
location.hash = _hash;
}
// === EVENT HANDLERS ===
[handleClick](event) {
event.preventDefault();
this.$contextNode.scrollTo(
this.$referenceNode,
this._duration,
{offset: -this._offset ,axis: 'y'}
);
if (!this._hideFromURL) {
this._updateHash(this._reference);
}
}
}
export default AnchorTrigger;