Interim commit.
[yaffs-website] / web / modules / contrib / blazy / js / dblazy.js
1 /**
2  * @file
3  * Cherries by @toddmotto, @cferdinandi, @adamfschwartz, @daniellmb.
4  *
5  * @todo: Use Cash or Underscore when jQuery is dropped by supported plugins.
6  */
7
8 /* global window, document, define, module */
9 (function (root, factory) {
10
11   'use strict';
12
13   // Inspired by https://github.com/addyosmani/memoize.js/blob/master/memoize.js
14   if (typeof define === 'function' && define.amd) {
15     // AMD. Register as an anonymous module.
16     define([], factory);
17   }
18   else if (typeof exports === 'object') {
19     // Node. Does not work with strict CommonJS, but only CommonJS-like
20     // environments that support module.exports, like Node.
21     module.exports = factory();
22   }
23   else {
24     // Browser globals (root is window).
25     root.dBlazy = factory();
26   }
27 })(this, function () {
28
29   'use strict';
30
31   /**
32    * Object for public APIs where dBlazy stands for drupalBlazy.
33    *
34    * @namespace
35    */
36   var dBlazy = {};
37
38   /**
39    * Check if the given element matches the selector.
40    *
41    * @name dBlazy.matches
42    *
43    * @param {Element} elem
44    *   The current element.
45    * @param {String} selector
46    *   Selector to match against (class, ID, data attribute, or tag).
47    *
48    * @return {Boolean}
49    *   Returns true if found, else false.
50    *
51    * @see http://caniuse.com/#feat=matchesselector
52    * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/matches
53    */
54   dBlazy.matches = function (elem, selector) {
55     // Element.matches() polyfill.
56     var p = Element.prototype;
57     if (!p.matches) {
58       p.matches =
59         p.matchesSelector ||
60         p.mozMatchesSelector ||
61         p.msMatchesSelector ||
62         p.oMatchesSelector ||
63         p.webkitMatchesSelector ||
64         function (s) {
65           var matches = (this.document || this.ownerDocument).querySelectorAll(s);
66           var i = matches.length;
67           while (--i >= 0 && matches.item(i) !== this) {
68             // Empty block to satisfy coder and eslint.
69           }
70           return i > -1;
71         };
72     }
73
74     // Check if matches.
75     if (elem.matches(selector)) {
76       return true;
77     }
78
79     return false;
80   };
81
82   /**
83    * Get the closest matching element up the DOM tree.
84    *
85    * Inspired by Chris Ferdinandi, http://github.com/cferdinandi/smooth-scroll.
86    *
87    * @name dBlazy.closest
88    *
89    * @param {Element} elem
90    *   Starting element.
91    * @param {String} selector
92    *   Selector to match against (class, ID, data attribute, or tag).
93    *
94    * @return {Boolean|Element}
95    *   Returns null if not match found.
96    *
97    * @see http://caniuse.com/#feat=element-closest
98    * @see http://caniuse.com/#feat=matchesselector
99    * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/matches
100    */
101   dBlazy.closest = function (elem, selector) {
102     for (; elem && elem !== document; elem = elem.parentNode) {
103       if (dBlazy.matches(elem, selector)) {
104         return elem;
105       }
106     }
107
108     return null;
109   };
110
111   /**
112    * Returns a new object after merging two, or more objects.
113    *
114    * Inspired by @adamfschwartz, @zackbloom, http://youmightnotneedjquery.com.
115    *
116    * @name dBlazy.extend
117    *
118    * @param {Object} out
119    *   The objects to merge together.
120    *
121    * @return {Object}
122    *   Merged values of defaults and options.
123    */
124   dBlazy.extend = Object.assign || function (out) {
125     out = out || {};
126
127     for (var i = 1, len = arguments.length; i < len; i++) {
128       if (!arguments[i]) {
129         continue;
130       }
131
132       for (var key in arguments[i]) {
133         if (arguments[i].hasOwnProperty(key)) {
134           out[key] = arguments[i][key];
135         }
136       }
137     }
138
139     return out;
140   };
141
142   /**
143    * A simple forEach() implementation for Arrays, Objects and NodeLists.
144    *
145    * @name dBlazy.forEach
146    *
147    * @author Todd Motto
148    * @link https://github.com/toddmotto/foreach
149    *
150    * @param {Array|Object|NodeList} collection
151    *   Collection of items to iterate.
152    * @param {Function} callback
153    *   Callback function for each iteration.
154    * @param {Array|Object|NodeList} scope
155    *   Object/NodeList/Array that forEach is iterating over (aka `this`).
156    */
157   dBlazy.forEach = function (collection, callback, scope) {
158     var proto = Object.prototype;
159     if (proto.toString.call(collection) === '[object Object]') {
160       for (var prop in collection) {
161         if (proto.hasOwnProperty.call(collection, prop)) {
162           callback.call(scope, collection[prop], prop, collection);
163         }
164       }
165     }
166     else {
167       for (var i = 0, len = collection.length; i < len; i++) {
168         callback.call(scope, collection[i], i, collection);
169       }
170     }
171   };
172
173   /**
174    * A simple hasClass wrapper.
175    *
176    * @name dBlazy.hasClass
177    *
178    * @param {Element} el
179    *   The HTML element.
180    * @param {String} name
181    *   The class name.
182    *
183    * @return {bool}
184    *   True if of of the method is supported.
185    */
186   dBlazy.hasClass = function (el, name) {
187     if (el.classList) {
188       return el.classList.contains(name);
189     }
190     else {
191       return el.className.indexOf(name) !== -1;
192     }
193   };
194
195   /**
196    * A simple wrapper for event delegation like jQuery.on().
197    *
198    * Inspired by http://stackoverflow.com/questions/30880757/
199    * javascript-equivalent-to-on.
200    *
201    * @name dBlazy.on
202    *
203    * @param {Element} elm
204    *   The parent HTML element.
205    * @param {String} eventName
206    *   The event name to trigger.
207    * @param {String} childEl
208    *   Child selector to match against (class, ID, data attribute, or tag).
209    * @param {Function} callback
210    *   The callback function.
211    */
212   dBlazy.on = function (elm, eventName, childEl, callback) {
213     elm.addEventListener(eventName, function (event) {
214       var t = event.target;
215       while (t && t !== this) {
216         if (dBlazy.matches(t, childEl)) {
217           callback.call(t, event);
218         }
219         t = t.parentNode;
220       }
221     });
222   };
223
224   /**
225    * Executes a function once.
226    *
227    * @name dBlazy.once
228    *
229    * @author Daniel Lamb <dlamb.open.source@gmail.com>
230    * @link https://github.com/daniellmb/once.js
231    *
232    * @param {Function} fn
233    *   The executed function.
234    *
235    * @return {Object}
236    *   The function result.
237    */
238   dBlazy.once = function (fn) {
239     var result;
240     var ran = false;
241     return function proxy() {
242       if (ran) {
243         return result;
244       }
245       ran = true;
246       result = fn.apply(this, arguments);
247       // For garbage collection.
248       fn = null;
249       return result;
250     };
251   };
252
253   /**
254    * A simple wrapper for JSON.parse() for string within data-* attributes.
255    *
256    * @name dBlazy.parse
257    *
258    * @param {String} str
259    *   The string to convert into JSON object.
260    *
261    * @return {Object|Boolean}
262    *   The JSON object, or false in case invalid.
263    */
264   dBlazy.parse = function (str) {
265     try {
266       return JSON.parse(str);
267     }
268     catch (e) {
269       return false;
270     }
271   };
272
273   /**
274    * A simple wrapper to delay callback function on window resize.
275    *
276    * @name dBlazy.resize
277    *
278    * @link https://github.com/louisremi/jquery-smartresize
279    *
280    * @param {Function} c
281    *   The callback function.
282    * @param {Int} t
283    *   The timeout.
284    *
285    * @return {Function}
286    *   The callback function.
287    */
288   dBlazy.resize = function (c, t) {
289     window.onresize = function () {
290       window.clearTimeout(t);
291       t = window.setTimeout(c, 200);
292     };
293     return c;
294   };
295
296   /**
297    * A simple wrapper for triggering event like jQuery.trigger().
298    *
299    * @name dBlazy.trigger
300    *
301    * @param {Element} elm
302    *   The HTML element.
303    * @param {String} eventName
304    *   The event name to trigger.
305    * @param {Object} custom
306    *   The optional object passed into a custom event.
307    * @param {String} type
308    *   Default to MouseEvents, can be either:
309    *     MouseEvents: click, mousedown, mouseup.
310    *     HTMLEvents: focus, change, blur, select.
311    *
312    * @see https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
313    * @todo: See if any consistent way for both custom and native events.
314    */
315   dBlazy.trigger = function (elm, eventName, custom, type) {
316     var event;
317     custom = custom || {};
318     type = type || 'MouseEvents';
319
320     var addEvent = function (eventName, data) {
321       data = data || {};
322       // @todo: Use Event constructor, pending as not supported by all IEs.
323       event = document.createEvent(data && typeof data === 'object' ? 'Event' : type);
324       event.initEvent(eventName, true, true, data);
325
326       return event;
327     };
328
329     // IE >= 9 compat, else SCRIPT445: Object doesn't support this action.
330     // https://msdn.microsoft.com/library/ff975299(v=vs.85).aspx
331     try {
332       event = custom ? new CustomEvent(eventName, {detail: custom}) : addEvent(eventName);
333     }
334     catch (e) {
335       event = addEvent(eventName, custom);
336     }
337
338     elm.dispatchEvent(event);
339   };
340
341   return dBlazy;
342
343 });