UNPKG

9.24 kBJavaScriptView Raw
1'use strict';
2
3import $ from 'jquery';
4import { MediaQuery } from './foundation.util.mediaQuery';
5import { onImagesLoaded } from './foundation.util.imageLoader';
6import { GetYoDigits } from './foundation.util.core';
7import { Plugin } from './foundation.plugin';
8
9/**
10 * Equalizer module.
11 * @module foundation.equalizer
12 * @requires foundation.util.mediaQuery
13 * @requires foundation.util.imageLoader if equalizer contains images
14 */
15
16class Equalizer extends Plugin {
17 /**
18 * Creates a new instance of Equalizer.
19 * @class
20 * @name Equalizer
21 * @fires Equalizer#init
22 * @param {Object} element - jQuery object to add the trigger to.
23 * @param {Object} options - Overrides to the default plugin settings.
24 */
25 _setup(element, options){
26 this.$element = element;
27 this.options = $.extend({}, Equalizer.defaults, this.$element.data(), options);
28 this.className = 'Equalizer'; // ie9 back compat
29
30 this._init();
31 }
32
33 /**
34 * Initializes the Equalizer plugin and calls functions to get equalizer functioning on load.
35 * @private
36 */
37 _init() {
38 var eqId = this.$element.attr('data-equalizer') || '';
39 var $watched = this.$element.find(`[data-equalizer-watch="${eqId}"]`);
40
41 MediaQuery._init();
42
43 this.$watched = $watched.length ? $watched : this.$element.find('[data-equalizer-watch]');
44 this.$element.attr('data-resize', (eqId || GetYoDigits(6, 'eq')));
45 this.$element.attr('data-mutate', (eqId || GetYoDigits(6, 'eq')));
46
47 this.hasNested = this.$element.find('[data-equalizer]').length > 0;
48 this.isNested = this.$element.parentsUntil(document.body, '[data-equalizer]').length > 0;
49 this.isOn = false;
50 this._bindHandler = {
51 onResizeMeBound: this._onResizeMe.bind(this),
52 onPostEqualizedBound: this._onPostEqualized.bind(this)
53 };
54
55 var imgs = this.$element.find('img');
56 var tooSmall;
57 if(this.options.equalizeOn){
58 tooSmall = this._checkMQ();
59 $(window).on('changed.zf.mediaquery', this._checkMQ.bind(this));
60 }else{
61 this._events();
62 }
63 if((tooSmall !== undefined && tooSmall === false) || tooSmall === undefined){
64 if(imgs.length){
65 onImagesLoaded(imgs, this._reflow.bind(this));
66 }else{
67 this._reflow();
68 }
69 }
70 }
71
72 /**
73 * Removes event listeners if the breakpoint is too small.
74 * @private
75 */
76 _pauseEvents() {
77 this.isOn = false;
78 this.$element.off({
79 '.zf.equalizer': this._bindHandler.onPostEqualizedBound,
80 'resizeme.zf.trigger': this._bindHandler.onResizeMeBound,
81 'mutateme.zf.trigger': this._bindHandler.onResizeMeBound
82 });
83 }
84
85 /**
86 * function to handle $elements resizeme.zf.trigger, with bound this on _bindHandler.onResizeMeBound
87 * @private
88 */
89 _onResizeMe(e) {
90 this._reflow();
91 }
92
93 /**
94 * function to handle $elements postequalized.zf.equalizer, with bound this on _bindHandler.onPostEqualizedBound
95 * @private
96 */
97 _onPostEqualized(e) {
98 if(e.target !== this.$element[0]){ this._reflow(); }
99 }
100
101 /**
102 * Initializes events for Equalizer.
103 * @private
104 */
105 _events() {
106 var _this = this;
107 this._pauseEvents();
108 if(this.hasNested){
109 this.$element.on('postequalized.zf.equalizer', this._bindHandler.onPostEqualizedBound);
110 }else{
111 this.$element.on('resizeme.zf.trigger', this._bindHandler.onResizeMeBound);
112 this.$element.on('mutateme.zf.trigger', this._bindHandler.onResizeMeBound);
113 }
114 this.isOn = true;
115 }
116
117 /**
118 * Checks the current breakpoint to the minimum required size.
119 * @private
120 */
121 _checkMQ() {
122 var tooSmall = !MediaQuery.is(this.options.equalizeOn);
123 if(tooSmall){
124 if(this.isOn){
125 this._pauseEvents();
126 this.$watched.css('height', 'auto');
127 }
128 }else{
129 if(!this.isOn){
130 this._events();
131 }
132 }
133 return tooSmall;
134 }
135
136 /**
137 * A noop version for the plugin
138 * @private
139 */
140 _killswitch() {
141 return;
142 }
143
144 /**
145 * Calls necessary functions to update Equalizer upon DOM change
146 * @private
147 */
148 _reflow() {
149 if(!this.options.equalizeOnStack){
150 if(this._isStacked()){
151 this.$watched.css('height', 'auto');
152 return false;
153 }
154 }
155 if (this.options.equalizeByRow) {
156 this.getHeightsByRow(this.applyHeightByRow.bind(this));
157 }else{
158 this.getHeights(this.applyHeight.bind(this));
159 }
160 }
161
162 /**
163 * Manually determines if the first 2 elements are *NOT* stacked.
164 * @private
165 */
166 _isStacked() {
167 if (!this.$watched[0] || !this.$watched[1]) {
168 return true;
169 }
170 return this.$watched[0].getBoundingClientRect().top !== this.$watched[1].getBoundingClientRect().top;
171 }
172
173 /**
174 * Finds the outer heights of children contained within an Equalizer parent and returns them in an array
175 * @param {Function} cb - A non-optional callback to return the heights array to.
176 * @returns {Array} heights - An array of heights of children within Equalizer container
177 */
178 getHeights(cb) {
179 var heights = [];
180 for(var i = 0, len = this.$watched.length; i < len; i++){
181 this.$watched[i].style.height = 'auto';
182 heights.push(this.$watched[i].offsetHeight);
183 }
184 cb(heights);
185 }
186
187 /**
188 * Finds the outer heights of children contained within an Equalizer parent and returns them in an array
189 * @param {Function} cb - A non-optional callback to return the heights array to.
190 * @returns {Array} groups - An array of heights of children within Equalizer container grouped by row with element,height and max as last child
191 */
192 getHeightsByRow(cb) {
193 var lastElTopOffset = (this.$watched.length ? this.$watched.first().offset().top : 0),
194 groups = [],
195 group = 0;
196 //group by Row
197 groups[group] = [];
198 for(var i = 0, len = this.$watched.length; i < len; i++){
199 this.$watched[i].style.height = 'auto';
200 //maybe could use this.$watched[i].offsetTop
201 var elOffsetTop = $(this.$watched[i]).offset().top;
202 if (elOffsetTop!=lastElTopOffset) {
203 group++;
204 groups[group] = [];
205 lastElTopOffset=elOffsetTop;
206 }
207 groups[group].push([this.$watched[i],this.$watched[i].offsetHeight]);
208 }
209
210 for (var j = 0, ln = groups.length; j < ln; j++) {
211 var heights = $(groups[j]).map(function(){ return this[1]; }).get();
212 var max = Math.max.apply(null, heights);
213 groups[j].push(max);
214 }
215 cb(groups);
216 }
217
218 /**
219 * Changes the CSS height property of each child in an Equalizer parent to match the tallest
220 * @param {array} heights - An array of heights of children within Equalizer container
221 * @fires Equalizer#preequalized
222 * @fires Equalizer#postequalized
223 */
224 applyHeight(heights) {
225 var max = Math.max.apply(null, heights);
226 /**
227 * Fires before the heights are applied
228 * @event Equalizer#preequalized
229 */
230 this.$element.trigger('preequalized.zf.equalizer');
231
232 this.$watched.css('height', max);
233
234 /**
235 * Fires when the heights have been applied
236 * @event Equalizer#postequalized
237 */
238 this.$element.trigger('postequalized.zf.equalizer');
239 }
240
241 /**
242 * Changes the CSS height property of each child in an Equalizer parent to match the tallest by row
243 * @param {array} groups - An array of heights of children within Equalizer container grouped by row with element,height and max as last child
244 * @fires Equalizer#preequalized
245 * @fires Equalizer#preequalizedrow
246 * @fires Equalizer#postequalizedrow
247 * @fires Equalizer#postequalized
248 */
249 applyHeightByRow(groups) {
250 /**
251 * Fires before the heights are applied
252 */
253 this.$element.trigger('preequalized.zf.equalizer');
254 for (var i = 0, len = groups.length; i < len ; i++) {
255 var groupsILength = groups[i].length,
256 max = groups[i][groupsILength - 1];
257 if (groupsILength<=2) {
258 $(groups[i][0][0]).css({'height':'auto'});
259 continue;
260 }
261 /**
262 * Fires before the heights per row are applied
263 * @event Equalizer#preequalizedrow
264 */
265 this.$element.trigger('preequalizedrow.zf.equalizer');
266 for (var j = 0, lenJ = (groupsILength-1); j < lenJ ; j++) {
267 $(groups[i][j][0]).css({'height':max});
268 }
269 /**
270 * Fires when the heights per row have been applied
271 * @event Equalizer#postequalizedrow
272 */
273 this.$element.trigger('postequalizedrow.zf.equalizer');
274 }
275 /**
276 * Fires when the heights have been applied
277 */
278 this.$element.trigger('postequalized.zf.equalizer');
279 }
280
281 /**
282 * Destroys an instance of Equalizer.
283 * @function
284 */
285 _destroy() {
286 this._pauseEvents();
287 this.$watched.css('height', 'auto');
288 }
289}
290
291/**
292 * Default settings for plugin
293 */
294Equalizer.defaults = {
295 /**
296 * Enable height equalization when stacked on smaller screens.
297 * @option
298 * @type {boolean}
299 * @default false
300 */
301 equalizeOnStack: false,
302 /**
303 * Enable height equalization row by row.
304 * @option
305 * @type {boolean}
306 * @default false
307 */
308 equalizeByRow: false,
309 /**
310 * String representing the minimum breakpoint size the plugin should equalize heights on.
311 * @option
312 * @type {string}
313 * @default ''
314 */
315 equalizeOn: ''
316};
317
318export {Equalizer};