319 lines
9.0 KiB
JavaScript
319 lines
9.0 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
import $ from 'jquery';
|
||
|
import { MediaQuery } from './foundation.util.mediaQuery';
|
||
|
import { onImagesLoaded } from './foundation.util.imageLoader';
|
||
|
import { GetYoDigits } from './foundation.core.utils';
|
||
|
import { Plugin } from './foundation.core.plugin';
|
||
|
|
||
|
/**
|
||
|
* Equalizer module.
|
||
|
* @module foundation.equalizer
|
||
|
* @requires foundation.util.mediaQuery
|
||
|
* @requires foundation.util.imageLoader if equalizer contains images
|
||
|
*/
|
||
|
|
||
|
class Equalizer extends Plugin {
|
||
|
/**
|
||
|
* Creates a new instance of Equalizer.
|
||
|
* @class
|
||
|
* @name Equalizer
|
||
|
* @fires Equalizer#init
|
||
|
* @param {Object} element - jQuery object to add the trigger to.
|
||
|
* @param {Object} options - Overrides to the default plugin settings.
|
||
|
*/
|
||
|
_setup(element, options){
|
||
|
this.$element = element;
|
||
|
this.options = $.extend({}, Equalizer.defaults, this.$element.data(), options);
|
||
|
this.className = 'Equalizer'; // ie9 back compat
|
||
|
|
||
|
this._init();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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}"]`);
|
||
|
|
||
|
MediaQuery._init();
|
||
|
|
||
|
this.$watched = $watched.length ? $watched : this.$element.find('[data-equalizer-watch]');
|
||
|
this.$element.attr('data-resize', (eqId || GetYoDigits(6, 'eq')));
|
||
|
this.$element.attr('data-mutate', (eqId || 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((typeof tooSmall !== 'undefined' && tooSmall === false) || typeof tooSmall === 'undefined'){
|
||
|
if(imgs.length){
|
||
|
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,
|
||
|
'mutateme.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.$element.on('mutateme.zf.trigger', this._bindHandler.onResizeMeBound);
|
||
|
}
|
||
|
this.isOn = true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Checks the current breakpoint to the minimum required size.
|
||
|
* @private
|
||
|
*/
|
||
|
_checkMQ() {
|
||
|
var tooSmall = !MediaQuery.is(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() {
|
||
|
if (!this.$watched[0] || !this.$watched[1]) {
|
||
|
return true;
|
||
|
}
|
||
|
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');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Default settings for plugin
|
||
|
*/
|
||
|
Equalizer.defaults = {
|
||
|
/**
|
||
|
* Enable height equalization when stacked on smaller screens.
|
||
|
* @option
|
||
|
* @type {boolean}
|
||
|
* @default false
|
||
|
*/
|
||
|
equalizeOnStack: false,
|
||
|
/**
|
||
|
* Enable height equalization row by row.
|
||
|
* @option
|
||
|
* @type {boolean}
|
||
|
* @default false
|
||
|
*/
|
||
|
equalizeByRow: false,
|
||
|
/**
|
||
|
* String representing the minimum breakpoint size the plugin should equalize heights on.
|
||
|
* @option
|
||
|
* @type {string}
|
||
|
* @default ''
|
||
|
*/
|
||
|
equalizeOn: ''
|
||
|
};
|
||
|
|
||
|
export {Equalizer};
|