// 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; }; };