'use strict'; !function($) { /** * Equalizer module. * @module foundation.equalizer */ class Equalizer { /** * Creates a new instance of Equalizer. * @class * @fires Equalizer#init * @param {Object} element - jQuery object to add the trigger to. * @param {Object} options - Overrides to the default plugin settings. */ constructor(element, options){ this.$element = element; this.options = $.extend({}, Equalizer.defaults, this.$element.data(), options); this._init(); Foundation.registerPlugin(this, 'Equalizer'); } /** * Initializes the Equalizer plugin and calls functions to get equalizer functioning on load. * @private */ _init() { var eqId = this.$element.attr('data-equalizer') || ''; var $watched = this.$element.find(`[data-equalizer-watch="${eqId}"]`); this.$watched = $watched.length ? $watched : this.$element.find('[data-equalizer-watch]'); this.$element.attr('data-resize', (eqId || Foundation.GetYoDigits(6, 'eq'))); this.hasNested = this.$element.find('[data-equalizer]').length > 0; this.isNested = this.$element.parentsUntil(document.body, '[data-equalizer]').length > 0; this.isOn = false; this._bindHandler = { onResizeMeBound: this._onResizeMe.bind(this), onPostEqualizedBound: this._onPostEqualized.bind(this) }; var imgs = this.$element.find('img'); var tooSmall; if(this.options.equalizeOn){ tooSmall = this._checkMQ(); $(window).on('changed.zf.mediaquery', this._checkMQ.bind(this)); }else{ this._events(); } if((tooSmall !== undefined && tooSmall === false) || tooSmall === undefined){ if(imgs.length){ Foundation.onImagesLoaded(imgs, this._reflow.bind(this)); }else{ this._reflow(); } } } /** * Removes event listeners if the breakpoint is too small. * @private */ _pauseEvents() { this.isOn = false; this.$element.off({ '.zf.equalizer': this._bindHandler.onPostEqualizedBound, 'resizeme.zf.trigger': this._bindHandler.onResizeMeBound }); } /** * function to handle $elements resizeme.zf.trigger, with bound this on _bindHandler.onResizeMeBound * @private */ _onResizeMe(e) { this._reflow(); } /** * function to handle $elements postequalized.zf.equalizer, with bound this on _bindHandler.onPostEqualizedBound * @private */ _onPostEqualized(e) { if(e.target !== this.$element[0]){ this._reflow(); } } /** * Initializes events for Equalizer. * @private */ _events() { var _this = this; this._pauseEvents(); if(this.hasNested){ this.$element.on('postequalized.zf.equalizer', this._bindHandler.onPostEqualizedBound); }else{ this.$element.on('resizeme.zf.trigger', this._bindHandler.onResizeMeBound); } this.isOn = true; } /** * Checks the current breakpoint to the minimum required size. * @private */ _checkMQ() { var tooSmall = !Foundation.MediaQuery.atLeast(this.options.equalizeOn); if(tooSmall){ if(this.isOn){ this._pauseEvents(); this.$watched.css('height', 'auto'); } }else{ if(!this.isOn){ this._events(); } } return tooSmall; } /** * A noop version for the plugin * @private */ _killswitch() { return; } /** * Calls necessary functions to update Equalizer upon DOM change * @private */ _reflow() { if(!this.options.equalizeOnStack){ if(this._isStacked()){ this.$watched.css('height', 'auto'); return false; } } if (this.options.equalizeByRow) { this.getHeightsByRow(this.applyHeightByRow.bind(this)); }else{ this.getHeights(this.applyHeight.bind(this)); } } /** * Manually determines if the first 2 elements are *NOT* stacked. * @private */ _isStacked() { return this.$watched[0].getBoundingClientRect().top !== this.$watched[1].getBoundingClientRect().top; } /** * Finds the outer heights of children contained within an Equalizer parent and returns them in an array * @param {Function} cb - A non-optional callback to return the heights array to. * @returns {Array} heights - An array of heights of children within Equalizer container */ getHeights(cb) { var heights = []; for(var i = 0, len = this.$watched.length; i < len; i++){ this.$watched[i].style.height = 'auto'; heights.push(this.$watched[i].offsetHeight); } cb(heights); } /** * Finds the outer heights of children contained within an Equalizer parent and returns them in an array * @param {Function} cb - A non-optional callback to return the heights array to. * @returns {Array} groups - An array of heights of children within Equalizer container grouped by row with element,height and max as last child */ getHeightsByRow(cb) { var lastElTopOffset = (this.$watched.length ? this.$watched.first().offset().top : 0), groups = [], group = 0; //group by Row groups[group] = []; for(var i = 0, len = this.$watched.length; i < len; i++){ this.$watched[i].style.height = 'auto'; //maybe could use this.$watched[i].offsetTop var elOffsetTop = $(this.$watched[i]).offset().top; if (elOffsetTop!=lastElTopOffset) { group++; groups[group] = []; lastElTopOffset=elOffsetTop; } groups[group].push([this.$watched[i],this.$watched[i].offsetHeight]); } for (var j = 0, ln = groups.length; j < ln; j++) { var heights = $(groups[j]).map(function(){ return this[1]; }).get(); var max = Math.max.apply(null, heights); groups[j].push(max); } cb(groups); } /** * Changes the CSS height property of each child in an Equalizer parent to match the tallest * @param {array} heights - An array of heights of children within Equalizer container * @fires Equalizer#preequalized * @fires Equalizer#postequalized */ applyHeight(heights) { var max = Math.max.apply(null, heights); /** * Fires before the heights are applied * @event Equalizer#preequalized */ this.$element.trigger('preequalized.zf.equalizer'); this.$watched.css('height', max); /** * Fires when the heights have been applied * @event Equalizer#postequalized */ this.$element.trigger('postequalized.zf.equalizer'); } /** * Changes the CSS height property of each child in an Equalizer parent to match the tallest by row * @param {array} groups - An array of heights of children within Equalizer container grouped by row with element,height and max as last child * @fires Equalizer#preequalized * @fires Equalizer#preequalizedRow * @fires Equalizer#postequalizedRow * @fires Equalizer#postequalized */ applyHeightByRow(groups) { /** * Fires before the heights are applied */ this.$element.trigger('preequalized.zf.equalizer'); for (var i = 0, len = groups.length; i < len ; i++) { var groupsILength = groups[i].length, max = groups[i][groupsILength - 1]; if (groupsILength<=2) { $(groups[i][0][0]).css({'height':'auto'}); continue; } /** * Fires before the heights per row are applied * @event Equalizer#preequalizedRow */ this.$element.trigger('preequalizedrow.zf.equalizer'); for (var j = 0, lenJ = (groupsILength-1); j < lenJ ; j++) { $(groups[i][j][0]).css({'height':max}); } /** * Fires when the heights per row have been applied * @event Equalizer#postequalizedRow */ this.$element.trigger('postequalizedrow.zf.equalizer'); } /** * Fires when the heights have been applied */ this.$element.trigger('postequalized.zf.equalizer'); } /** * Destroys an instance of Equalizer. * @function */ destroy() { this._pauseEvents(); this.$watched.css('height', 'auto'); Foundation.unregisterPlugin(this); } } /** * Default settings for plugin */ Equalizer.defaults = { /** * Enable height equalization when stacked on smaller screens. * @option * @example true */ equalizeOnStack: true, /** * Enable height equalization row by row. * @option * @example false */ equalizeByRow: false, /** * String representing the minimum breakpoint size the plugin should equalize heights on. * @option * @example 'medium' */ equalizeOn: '' }; // Window exports Foundation.plugin(Equalizer, 'Equalizer'); }(jQuery);