(function () { 'use strict'; // queryTarget hash delimiter var HASH_DELIM = '#::'; /** * @ref https://gist.github.com/nickolasburr/9ebee93c0ac155cc25d54eaf44952a0e */ /** * Exception handling methods */ var Exception = {}; // throw TypeError exception with message Exception.throwTypeError = function (message) { throw new TypeError(message); }; // throw Error exception with message Exception.throwGenericError = function (message) { throw new Error(message); }; /** * Utility methods */ var Utils = Object.create(Exception); // coerce `value` to boolean Utils.toBool = function (value) { return !!(value); }; // coerce `value` to number Utils.toNumber = function (value) { return +(value); }; // coerce `value` to string Utils.toString = function (value) { return ('' + value); }; // coerce `value` to array Utils.toArray = function (value, sep) { sep = sep || ''; if (!this.isScalar(value)) { this.throwTypeError('`Utils.toArray` -> `value` must be a scalar, this is a[n] ' + this.getType(value)); } return this.toString(value).split(sep); }; // get primitive type of `arg` Utils.getType = function (arg) { return (typeof arg); }; /** * determine if `arg` is null * * @notes This performs strict checking against `arg`, * so even if `arg` is a falsey value (e.g. '', 0, false, undefined), * it will only return true if `arg` contains the null object */ Utils.isNull = function (arg) { return this.toBool(arg === null); }; /** * determine if `arg` is undefined * * @notes Like `Utils.isNull`, this performs a strict checking against `arg`, * so even if `arg` is a falsey value (e.g. '', 0, false, null), * it will only return true if `arg` is actually undefined */ Utils.isUndefined = function (arg) { return (this.getType(arg) === 'undefined'); }; /** * determine if `arg` is defined (syntactic sugar for negated `Utils.isUndefined`) * * @notes See `Utils.isUndefined` for important notes */ Utils.isDefined = function (arg) { return !this.isUndefined(arg); }; /** * determine if `arg` is an empty string */ Utils.isEmpty = function (arg) { return this.toBool(arg === ''); }; // determine if `source` is an instance of `target` Utils.isInstanceOf = function (source, target) { return this.toBool(source instanceof target); }; /** * determine if `obj` is of type 'object' * * @notes this is a **very** loose check on the type 'object', e.g. * it will return true for an object literal, object instance, * array literal, array instance, HTMLElement, Node, and so on... */ Utils.isObject = function (obj) { return this.isInstanceOf(obj, Object); }; /** * determine if `obj` is an object constructed from the native * 'Object' prototype and not a different object constructor */ Utils.isObjectNative = function (obj) { return this.toBool(this.isObject(obj) && Object.getPrototypeOf(obj).constructor.name === 'Object'); }; // determine if object is empty (has zero properties) Utils.isObjectEmpty = function (obj) { if (!this.isObject(obj)) { this.throwTypeError('`Utils.isObjectEmpty` -> `obj` must be an object, not ' + this.getType(obj)); } return !this.toBool(Object.keys(obj).length); }; // determine if `needle` is in `haystack` Utils.inArray = function (needle, haystack) { if (!this.isArray(haystack)) { this.throwTypeError('`Utils.inArray` -> `haystack` must be an array, not ' + this.getType(haystack)); } return this.toBool(haystack.indexOf(needle) > -1); }; // determine if `arr` is an Array Utils.isArray = function (arr) { return this.isInstanceOf(arr, Array); }; // determine if `func` is a Function Utils.isFunc = function (func) { return this.toBool(this.getType(func) === 'function' && this.isInstanceOf(func, Function)); }; // determine if `element` is a valid Element object Utils.isElement = function (element) { return this.isInstanceOf(element, Element); }; // determine if `node` is a valid Node object Utils.isNode = function (node) { return this.isInstanceOf(node, Node); }; // determine if `arg` is a scalar type Utils.isScalar = function (arg) { var scalars = [ 'string', 'number', 'boolean' ]; return this.inArray(this.getType(arg), scalars); }; // get parent node from Node object Utils.getParent = function (node) { if (!this.isNode(node)) { this.throwTypeError('`Utils.getParent` -> `node` must be a valid Node object!'); } return node.parentNode; }; // get keys from object Utils.getKeys = function (obj) { if (!this.isObject(obj)) { this.throwTypeError('`Utils.getKeys` -> `obj` must be an object, not ' + this.getType(obj)); } return Object.keys(obj); }; // get values from object Utils.getValues = function (obj) { if (!this.isObject(obj)) { this.throwTypeError('`Utils.getValues` -> `obj` must be an object, not ' + this.getType(obj)); } return Object.values(obj); }; // get index of element in array Utils.getIndexOf = function (needle, haystack) { // `needle` must be a scalar type in order for us to perform the lookup if (!this.isScalar(needle)) { this.throwTypeError('`Utils.getIndexOf` -> `needle` must be a scalar, this is a[n] ' + this.getType(needle)); } if (!this.isArray(haystack)) { this.throwTypeError('`Utils.getIndexOf` -> `haystack` must be an array, not ' + this.getType(haystack)); } return haystack.indexOf(needle); }; /** * @description Get last index of `arr` * @return {number} Last index */ Utils.getLastIndex = function (arr) { if (!this.isArray(arr)) { this.throwTypeError('`Utils.getLastIndex` -> `arr` must be an array, not ' + this.getType(arr)); } return this.toNumber(arr.length - 1); }; /** * Query methods */ var Query = Object.create(Utils); /** * `window.onload` event handler */ Query.onLoad = function () { var onHashChange = this.onHashChange.bind(this); // attach `window.onhashchange` event listener window.addEventListener('hashchange', onHashChange, false); this.setViewport(); }; /** * `window.onhashchange` event handler */ Query.onHashChange = function () { this.setViewport(); }; /** * Get specified URI hash */ Query.getHash = function () { return window.decodeURIComponent(document.location.hash); }; /** * Update viewport */ Query.setViewport = function () { // URI hash (e.g. -> '#::body>div.container') var hash = this.getHash(); // if there's no hash to check, we needn't continue onward if (this.isEmpty(hash)) { return null; } // URI hash, split into an array at intersection of `HASH_DELIM` var components = this.toArray(hash, HASH_DELIM), lastIndex = this.getLastIndex(components); // check for queryTarget syntax in hash, return if no match is found if (!this.toBool(lastIndex)) { return null; } // search document for an element matching the selector var element = document.querySelector(components[lastIndex]); // verify `element` is a valid element if (!this.isNode(element)) { return null; } // y-coordinate offset, measured from top of element to top of document var distance = (element.getBoundingClientRect().top + window.scrollY); window.setTimeout(function () { // scroll to the calculated y-coordinate offset window.scrollTo(0, distance); }, 10); return this; }; var loadHandler = Query.onLoad.bind(Query); window.addEventListener('load', loadHandler, false); }).call(this);