infojune/resources/sass/materialize/js/global.js

428 lines
12 KiB
JavaScript

// Required for Meteor package, the use of window prevents export by Meteor
(function(window) {
if (window.Package) {
M = {};
} else {
window.M = {};
}
// Check for jQuery
M.jQueryLoaded = !!window.jQuery;
})(window);
// AMD
if (typeof define === 'function' && define.amd) {
define('M', [], function() {
return M;
});
// Common JS
} else if (typeof exports !== 'undefined' && !exports.nodeType) {
if (typeof module !== 'undefined' && !module.nodeType && module.exports) {
exports = module.exports = M;
}
exports.default = M;
}
M.version = '1.0.0';
M.keys = {
TAB: 9,
ENTER: 13,
ESC: 27,
ARROW_UP: 38,
ARROW_DOWN: 40
};
/**
* TabPress Keydown handler
*/
M.tabPressed = false;
M.keyDown = false;
let docHandleKeydown = function(e) {
M.keyDown = true;
if (e.which === M.keys.TAB || e.which === M.keys.ARROW_DOWN || e.which === M.keys.ARROW_UP) {
M.tabPressed = true;
}
};
let docHandleKeyup = function(e) {
M.keyDown = false;
if (e.which === M.keys.TAB || e.which === M.keys.ARROW_DOWN || e.which === M.keys.ARROW_UP) {
M.tabPressed = false;
}
};
let docHandleFocus = function(e) {
if (M.keyDown) {
document.body.classList.add('keyboard-focused');
}
};
let docHandleBlur = function(e) {
document.body.classList.remove('keyboard-focused');
};
document.addEventListener('keydown', docHandleKeydown, true);
document.addEventListener('keyup', docHandleKeyup, true);
document.addEventListener('focus', docHandleFocus, true);
document.addEventListener('blur', docHandleBlur, true);
/**
* Initialize jQuery wrapper for plugin
* @param {Class} plugin javascript class
* @param {string} pluginName jQuery plugin name
* @param {string} classRef Class reference name
*/
M.initializeJqueryWrapper = function(plugin, pluginName, classRef) {
jQuery.fn[pluginName] = function(methodOrOptions) {
// Call plugin method if valid method name is passed in
if (plugin.prototype[methodOrOptions]) {
let params = Array.prototype.slice.call(arguments, 1);
// Getter methods
if (methodOrOptions.slice(0, 3) === 'get') {
let instance = this.first()[0][classRef];
return instance[methodOrOptions].apply(instance, params);
}
// Void methods
return this.each(function() {
let instance = this[classRef];
instance[methodOrOptions].apply(instance, params);
});
// Initialize plugin if options or no argument is passed in
} else if (typeof methodOrOptions === 'object' || !methodOrOptions) {
plugin.init(this, arguments[0]);
return this;
}
// Return error if an unrecognized method name is passed in
jQuery.error(`Method ${methodOrOptions} does not exist on jQuery.${pluginName}`);
};
};
/**
* Automatically initialize components
* @param {Element} context DOM Element to search within for components
*/
M.AutoInit = function(context) {
// Use document.body if no context is given
let root = !!context ? context : document.body;
let registry = {
Autocomplete: root.querySelectorAll('.autocomplete:not(.no-autoinit)'),
Carousel: root.querySelectorAll('.carousel:not(.no-autoinit)'),
Chips: root.querySelectorAll('.chips:not(.no-autoinit)'),
Collapsible: root.querySelectorAll('.collapsible:not(.no-autoinit)'),
Datepicker: root.querySelectorAll('.datepicker:not(.no-autoinit)'),
Dropdown: root.querySelectorAll('.dropdown-trigger:not(.no-autoinit)'),
Materialbox: root.querySelectorAll('.materialboxed:not(.no-autoinit)'),
Modal: root.querySelectorAll('.modal:not(.no-autoinit)'),
Parallax: root.querySelectorAll('.parallax:not(.no-autoinit)'),
Pushpin: root.querySelectorAll('.pushpin:not(.no-autoinit)'),
ScrollSpy: root.querySelectorAll('.scrollspy:not(.no-autoinit)'),
FormSelect: root.querySelectorAll('select:not(.no-autoinit)'),
Sidenav: root.querySelectorAll('.sidenav:not(.no-autoinit)'),
Tabs: root.querySelectorAll('.tabs:not(.no-autoinit)'),
TapTarget: root.querySelectorAll('.tap-target:not(.no-autoinit)'),
Timepicker: root.querySelectorAll('.timepicker:not(.no-autoinit)'),
Tooltip: root.querySelectorAll('.tooltipped:not(.no-autoinit)'),
FloatingActionButton: root.querySelectorAll('.fixed-action-btn:not(.no-autoinit)')
};
for (let pluginName in registry) {
let plugin = M[pluginName];
plugin.init(registry[pluginName]);
}
};
/**
* Generate approximated selector string for a jQuery object
* @param {jQuery} obj jQuery object to be parsed
* @returns {string}
*/
M.objectSelectorString = function(obj) {
let tagStr = obj.prop('tagName') || '';
let idStr = obj.attr('id') || '';
let classStr = obj.attr('class') || '';
return (tagStr + idStr + classStr).replace(/\s/g, '');
};
// Unique Random ID
M.guid = (function() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return function() {
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
};
})();
/**
* Escapes hash from special characters
* @param {string} hash String returned from this.hash
* @returns {string}
*/
M.escapeHash = function(hash) {
return hash.replace(/(:|\.|\[|\]|,|=|\/)/g, '\\$1');
};
M.elementOrParentIsFixed = function(element) {
let $element = $(element);
let $checkElements = $element.add($element.parents());
let isFixed = false;
$checkElements.each(function() {
if ($(this).css('position') === 'fixed') {
isFixed = true;
return false;
}
});
return isFixed;
};
/**
* @typedef {Object} Edges
* @property {Boolean} top If the top edge was exceeded
* @property {Boolean} right If the right edge was exceeded
* @property {Boolean} bottom If the bottom edge was exceeded
* @property {Boolean} left If the left edge was exceeded
*/
/**
* @typedef {Object} Bounding
* @property {Number} left left offset coordinate
* @property {Number} top top offset coordinate
* @property {Number} width
* @property {Number} height
*/
/**
* Escapes hash from special characters
* @param {Element} container Container element that acts as the boundary
* @param {Bounding} bounding element bounding that is being checked
* @param {Number} offset offset from edge that counts as exceeding
* @returns {Edges}
*/
M.checkWithinContainer = function(container, bounding, offset) {
let edges = {
top: false,
right: false,
bottom: false,
left: false
};
let containerRect = container.getBoundingClientRect();
// If body element is smaller than viewport, use viewport height instead.
let containerBottom =
container === document.body
? Math.max(containerRect.bottom, window.innerHeight)
: containerRect.bottom;
let scrollLeft = container.scrollLeft;
let scrollTop = container.scrollTop;
let scrolledX = bounding.left - scrollLeft;
let scrolledY = bounding.top - scrollTop;
// Check for container and viewport for each edge
if (scrolledX < containerRect.left + offset || scrolledX < offset) {
edges.left = true;
}
if (
scrolledX + bounding.width > containerRect.right - offset ||
scrolledX + bounding.width > window.innerWidth - offset
) {
edges.right = true;
}
if (scrolledY < containerRect.top + offset || scrolledY < offset) {
edges.top = true;
}
if (
scrolledY + bounding.height > containerBottom - offset ||
scrolledY + bounding.height > window.innerHeight - offset
) {
edges.bottom = true;
}
return edges;
};
M.checkPossibleAlignments = function(el, container, bounding, offset) {
let canAlign = {
top: true,
right: true,
bottom: true,
left: true,
spaceOnTop: null,
spaceOnRight: null,
spaceOnBottom: null,
spaceOnLeft: null
};
let containerAllowsOverflow = getComputedStyle(container).overflow === 'visible';
let containerRect = container.getBoundingClientRect();
let containerHeight = Math.min(containerRect.height, window.innerHeight);
let containerWidth = Math.min(containerRect.width, window.innerWidth);
let elOffsetRect = el.getBoundingClientRect();
let scrollLeft = container.scrollLeft;
let scrollTop = container.scrollTop;
let scrolledX = bounding.left - scrollLeft;
let scrolledYTopEdge = bounding.top - scrollTop;
let scrolledYBottomEdge = bounding.top + elOffsetRect.height - scrollTop;
// Check for container and viewport for left
canAlign.spaceOnRight = !containerAllowsOverflow
? containerWidth - (scrolledX + bounding.width)
: window.innerWidth - (elOffsetRect.left + bounding.width);
if (canAlign.spaceOnRight < 0) {
canAlign.left = false;
}
// Check for container and viewport for Right
canAlign.spaceOnLeft = !containerAllowsOverflow
? scrolledX - bounding.width + elOffsetRect.width
: elOffsetRect.right - bounding.width;
if (canAlign.spaceOnLeft < 0) {
canAlign.right = false;
}
// Check for container and viewport for Top
canAlign.spaceOnBottom = !containerAllowsOverflow
? containerHeight - (scrolledYTopEdge + bounding.height + offset)
: window.innerHeight - (elOffsetRect.top + bounding.height + offset);
if (canAlign.spaceOnBottom < 0) {
canAlign.top = false;
}
// Check for container and viewport for Bottom
canAlign.spaceOnTop = !containerAllowsOverflow
? scrolledYBottomEdge - (bounding.height - offset)
: elOffsetRect.bottom - (bounding.height + offset);
if (canAlign.spaceOnTop < 0) {
canAlign.bottom = false;
}
return canAlign;
};
M.getOverflowParent = function(element) {
if (element == null) {
return null;
}
if (element === document.body || getComputedStyle(element).overflow !== 'visible') {
return element;
}
return M.getOverflowParent(element.parentElement);
};
/**
* Gets id of component from a trigger
* @param {Element} trigger trigger
* @returns {string}
*/
M.getIdFromTrigger = function(trigger) {
let id = trigger.getAttribute('data-target');
if (!id) {
id = trigger.getAttribute('href');
if (id) {
id = id.slice(1);
} else {
id = '';
}
}
return id;
};
/**
* Multi browser support for document scroll top
* @returns {Number}
*/
M.getDocumentScrollTop = function() {
return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
};
/**
* Multi browser support for document scroll left
* @returns {Number}
*/
M.getDocumentScrollLeft = function() {
return window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0;
};
/**
* @typedef {Object} Edges
* @property {Boolean} top If the top edge was exceeded
* @property {Boolean} right If the right edge was exceeded
* @property {Boolean} bottom If the bottom edge was exceeded
* @property {Boolean} left If the left edge was exceeded
*/
/**
* @typedef {Object} Bounding
* @property {Number} left left offset coordinate
* @property {Number} top top offset coordinate
* @property {Number} width
* @property {Number} height
*/
/**
* Get time in ms
* @license https://raw.github.com/jashkenas/underscore/master/LICENSE
* @type {function}
* @return {number}
*/
let getTime =
Date.now ||
function() {
return new Date().getTime();
};
/**
* Returns a function, that, when invoked, will only be triggered at most once
* during a given window of time. Normally, the throttled function will run
* as much as it can, without ever going more than once per `wait` duration;
* but if you'd like to disable the execution on the leading edge, pass
* `{leading: false}`. To disable execution on the trailing edge, ditto.
* @license https://raw.github.com/jashkenas/underscore/master/LICENSE
* @param {function} func
* @param {number} wait
* @param {Object=} options
* @returns {Function}
*/
M.throttle = function(func, wait, options) {
let context, args, result;
let timeout = null;
let previous = 0;
options || (options = {});
let later = function() {
previous = options.leading === false ? 0 : getTime();
timeout = null;
result = func.apply(context, args);
context = args = null;
};
return function() {
let now = getTime();
if (!previous && options.leading === false) previous = now;
let remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0) {
clearTimeout(timeout);
timeout = null;
previous = now;
result = func.apply(context, args);
context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
};