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 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 // See https://developer.mozilla.org/en-US/docs/Web/API/Element/closest 39 if (!Element.prototype.matches) { 40 Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; 41 } 42 43 /** 44 * Check if the given element matches the selector. 45 * 46 * @name dBlazy.matches 47 * 48 * @param {Element} elem 49 * The current element. 50 * @param {String} selector 51 * Selector to match against (class, ID, data attribute, or tag). 52 * 53 * @return {Boolean} 54 * Returns true if found, else false. 55 * 56 * @see http://caniuse.com/#feat=matchesselector 57 * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/matches 58 */ 59 dBlazy.matches = function (elem, selector) { 60 // Check if matches, excluding HTMLDocument, see ::closest(). 61 if (elem.matches(selector)) { 62 return true; 63 } 64 65 return false; 66 }; 67 68 /** 69 * Returns device pixel ratio. 70 * 71 * @return {Integer} 72 * Returns the device pixel ratio. 73 */ 74 dBlazy.pixelRatio = function () { 75 return window.devicePixelRatio || 1; 76 }; 77 78 /** 79 * Returns cross-browser window width. 80 * 81 * @return {Integer} 82 * Returns the window width. 83 */ 84 dBlazy.windowWidth = function () { 85 return window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth || window.screen.width; 86 }; 87 88 /** 89 * Returns data from the current active window. 90 * 91 * When being resized, the browser gave no data about pixel ratio from desktop 92 * to mobile, not vice versa. Unless delayed for 4s+, not less, which is of 93 * course unacceptable. 94 * 95 * @name dBlazy.activeWidth 96 * 97 * @param {Object} dataset 98 * The dataset object must be keyed by window width. 99 * @param {Boolean} mobileFirst 100 * Whether to use min-width, or max-width. 101 * 102 * @return {mixed} 103 * Returns data from the current active window. 104 */ 105 dBlazy.activeWidth = function (dataset, mobileFirst) { 106 var me = this; 107 var keys = Object.keys(dataset); 108 var xs = keys[0]; 109 var xl = keys[keys.length - 1]; 110 var pr = (me.windowWidth() * me.pixelRatio()); 111 var ww = mobileFirst ? me.windowWidth() : pr; 112 var mw = function (w) { 113 // The picture wants <= (approximate), non-picture wants >=, wtf. 114 return mobileFirst ? parseInt(w) <= ww : parseInt(w) >= ww; 115 }; 116 117 var data = keys.filter(mw).map(function (v) { 118 return dataset[v]; 119 })[mobileFirst ? 'pop' : 'shift'](); 120 121 return typeof data === 'undefined' ? dataset[ww >= xl ? xl : xs] : data; 122 }; 123 124 /** 125 * Check if the HTML tag matches a specified string. 126 * 127 * @name dBlazy.equal 128 * 129 * @param {Element} el 130 * The element to compare. 131 * @param {String} str 132 * HTML tag to match against. 133 * 134 * @return {Boolean} 135 * Returns true if matches, else false. 136 */ 137 dBlazy.equal = function (el, str) { 138 return el !== null && el.nodeName.toLowerCase() === str; 139 }; 140 141 /** 142 * Get the closest matching element up the DOM tree. 143 * 144 * Inspired by Chris Ferdinandi, http://github.com/cferdinandi/smooth-scroll. 145 * 146 * @name dBlazy.closest 147 * 148 * @param {Element} el 149 * Starting element. 150 * @param {String} selector 151 * Selector to match against (class, ID, data attribute, or tag). 152 * 153 * @return {Boolean|Element} 154 * Returns null if not match found. 155 * 156 * @see http://caniuse.com/#feat=element-closest 157 * @see http://caniuse.com/#feat=matchesselector 158 * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/matches 159 */ 160 dBlazy.closest = function (el, selector) { 161 var parent; 162 while (el) { 163 parent = el.parentElement; 164 if (parent && parent.matches(selector)) { 165 return parent; 166 } 167 el = parent; 168 } 169 170 return null; 171 }; 172 173 /** 174 * Returns a new object after merging two, or more objects. 175 * 176 * Inspired by @adamfschwartz, @zackbloom, http://youmightnotneedjquery.com. 177 * 178 * @name dBlazy.extend 179 * 180 * @param {Object} out 181 * The objects to merge together. 182 * 183 * @return {Object} 184 * Merged values of defaults and options. 185 */ 186 dBlazy.extend = Object.assign || function (out) { 187 out = out || {}; 188 189 for (var i = 1, len = arguments.length; i < len; i++) { 190 if (!arguments[i]) { 191 continue; 192 } 193 194 for (var key in arguments[i]) { 195 if (Object.prototype.hasOwnProperty.call(arguments[i], key)) { 196 out[key] = arguments[i][key]; 197 } 198 } 199 } 200 201 return out; 202 }; 203 204 /**
205 * A simple forEach() implementation for Arrays, Objects and NodeLists. 206 * 207 * @name dBlazy.forEach 208 * 209 * @author Todd Motto 210 * @link https://github.com/toddmotto/foreach 211 * 212 * @param {Array|Object|NodeList} collection 213 * Collection of items to iterate. 214 * @param {Function} callback 215 * Callback function for each iteration. 216 * @param {Array|Object|NodeList} scope 217 * Object/NodeList/Array that forEach is iterating over (aka `this`). 218 */ 219 dBlazy.forEach = function (collection, callback, scope) { 220 var proto = Object.prototype; 221 if (proto.toString.call(collection) === '[object Object]') { 222 for (var prop in collection) { 223 if (proto.hasOwnProperty.call(collection, prop)) { 224 callback.call(scope, collection[prop], prop, collection); 225 } 226 } 227 } 228 else if (collection) { 229 for (var i = 0, len = collection.length; i < len; i++) { 230 callback.call(scope, collection[i], i, collection); 231 } 232 } 233 }; 234 235 /** 236 * A simple hasClass wrapper. 237 * 238 * @name dBlazy.hasClass 239 * 240 * @param {Element} el 241 * The HTML element. 242 * @param {String} name 243 * The class name. 244 * 245 * @return {bool} 246 * True if of of the method is supported. 247 * 248 * @todo remove for el.classList.contains() alone. 249 */ 250 dBlazy.hasClass = function (el, name) { 251 if (el.classList) { 252 return el.classList.contains(name); 253 } 254 else { 255 return el.className.indexOf(name) !== -1; 256 } 257 }; 258 259 /** 260 * A simple attributes wrapper. 261 * 262 * @name dBlazy.setAttr 263 * 264 * @param {Element} el 265 * The HTML element. 266 * @param {String} attr 267 * The attr name. 268 * @param {Boolean} remove 269 * True if should remove. 270 */ 271 dBlazy.setAttr = function (el, attr, remove) { 272 if (el.hasAttribute('data-' + attr)) { 273 var dataAttr = el.getAttribute('data-' + attr); 274 if (attr === 'src') { 275 el.src = dataAttr; 276 } 277 else { 278 el.setAttribute(attr, dataAttr); 279 } 280 281 if (remove) { 282 el.removeAttribute('data-' + attr); 283 } 284 } 285 }; 286 287 /** 288 * A simple attributes wrapper looping based on the given attributes. 289 * 290 * @name dBlazy.setAttrs 291 * 292 * @param {Element} el 293 * The HTML element. 294 * @param {Array} attrs 295 * The attr names. 296 * @param {Boolean} remove 297 * True if should remove. 298 */ 299 dBlazy.setAttrs = function (el, attrs, remove) { 300 var me = this; 301 302 me.forEach(attrs, function (src) { 303 me.setAttr(el, src, remove); 304 }); 305 }; 306 307 /** 308 * A simple attributes wrapper, looping based on sources (picture/ video). 309 * 310 * @name dBlazy.setAttrsWithSources 311 * 312 * @param {Element} el 313 * The starting HTML element. 314 * @param {String} attr 315 * The attr name, can be SRC or SRCSET. 316 * @param {Boolean} remove 317 * True if should remove. 318 */ 319 dBlazy.setAttrsWithSources = function (el, attr, remove) { 320 var me = this; 321 var parent = el.parentNode || null; 322 var isPicture = parent && me.equal(parent, 'picture'); 323 var targets = isPicture ? parent.getElementsByTagName('source') : el.getElementsByTagName('source'); 324 325 attr = attr || (isPicture ? 'srcset' : 'src'); 326 327 if (targets.length) { 328 me.forEach(targets, function (source) { 329 me.setAttr(source, attr, remove); 330 }); 331 } 332 }; 333 334 /** 335 * Updates CSS background with multi-breakpoint images. 336 * 337 * @name dBlazy.updateBg 338 * 339 * @param {Element} el 340 * The container HTML element. 341 * @param {Boolean} mobileFirst 342 * Whether to use min-width or max-width. 343 */ 344 dBlazy.updateBg = function (el, mobileFirst) { 345 var me = this; 346 var backgrounds = me.parse(el.getAttribute('data-backgrounds')); 347 348 if (backgrounds) { 349 var bg = me.activeWidth(backgrounds, mobileFirst); 350 if (bg && bg !== 'undefined') { 351 el.style.backgroundImage = 'url("' + bg.src + '")'; 352 353 // Allows to disable Aspect ratio if it has known/ fixed heights such as 354 // gridstack multi-size boxes. 355 if (bg.ratio && !el.classList.contains('b-noratio')) { 356 el.style.paddingBottom = bg.ratio + '%'; 357 } 358 } 359 } 360 }; 361 362 /** 363 * A simple removeAttribute wrapper. 364 * 365 * @name dBlazy.removeAttrs 366 * 367 * @param {Element} el 368 * The HTML element. 369 * @param {Array} attrs 370 * The attr names. 371 */ 372 dBlazy.removeAttrs = function (el, attrs) { 373 this.forEach(attrs, function (attr) { 374 el.removeAttribute('data-' + attr); 375 }); 376 }; 377 378 /** 379 * A simple wrapper for event delegation like jQuery.on(). 380 * 381 * Inspired by http://stackoverflow.com/questions/30880757/ 382 * javascript-equivalent-to-on. 383 * 384 * @name dBlazy.on 385 * 386 * @param {Element} elm 387 * The parent HTML element. 388 * @param {String} eventName 389 * The event name to trigger. 390 * @param {String} childEl 391 * Child selector to match against (class, ID, data attribute, or tag). 392 * @param {Function} callback 393 * The callback function. 394 * 395 * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener 396 */ 397 dBlazy.on = function (elm, eventName, childEl, callback) { 398 elm.addEventListener(eventName, function (event) { 399 var t = event.target; 400 event.delegateTarget = elm; 401 while (t && t !== this) { 402 if (dBlazy.matches(t, childEl)) { 403 callback.call(t, event); 404 } 405 t = t.parentNode; 406 } 407 }); 408 }; 409 410 /** 411 * A simple wrapper for addEventListener. 412 * 413 * Made public from original bLazy library. 414 * 415 * @name dBlazy.bindEvent 416 * 417 * @param {Element} el 418 * The HTML element. 419 * @param {String} type 420 * The event name to add. 421 * @param {Function} fn 422 * The callback function. 423 * @param {Object} params 424 * The optional param passed into a custom event. 425 * 426 * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener 427 * @todo remove old IE references after another check. 428 */ 429 dBlazy.bindEvent = function (el, type, fn, params) { 430 var defaults = {capture: false, passive: true}; 431 var extraParams = params ? this.extend(defaults, params) : defaults; 432 if (el.attachEvent) { 433 el.attachEvent('on' + type, fn, extraParams); 434 } 435 else { 436 el.addEventListener(type, fn, extraParams); 437 } 438 }; 439 440 /** 441 * A simple wrapper for removeEventListener. 442 * 443 * Made public from original bLazy library. 444 * 445 * @name dBlazy.unbindEvent 446 * 447 * @param {Element} el 448 * The HTML element. 449 * @param {String} type 450 * The event name to remove. 451 * @param {Function} fn 452 * The callback function. 453 * @param {Object} params 454 * The optional param passed into a custom event. 455 * 456 * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener 457 * @todo remove old IE references after another check. 458 */ 459 dBlazy.unbindEvent = function (el, type, fn, params) { 460 var defaults = {capture: false, passive: true}; 461 var extraParams = params ? this.extend(defaults, params) : defaults; 462 if (el.detachEvent) { 463 el.detachEvent('on' + type, fn, extraParams); 464 } 465 else { 466 el.removeEventListener(type, fn, extraParams); 467 } 468 }; 469 470 /** 471 * Executes a function once. 472 * 473 * @name dBlazy.once 474 * 475 * @author Daniel Lamb <[email protected]> 476 * @link https://github.com/daniellmb/once.js 477 * 478 * @param {Function} fn 479 * The executed function. 480 * 481 * @return {Object} 482 * The function result. 483 */ 484 dBlazy.once = function (fn) { 485 var result; 486 var ran = false;
487 return function proxy() { 488 if (ran) { 489 return result; 490 } 491 ran = true; 492 result = fn.apply(this, arguments); 493 // For garbage collection. 494 fn = null; 495 return result; 496 }; 497 }; 498 499 /** 500 * A simple wrapper for JSON.parse() for string within data-* attributes. 501 * 502 * @name dBlazy.parse 503 * 504 * @param {String} str 505 * The string to convert into JSON object. 506 * 507 * @return {Object|Boolean} 508 * The JSON object, or false in case invalid. 509 */ 510 dBlazy.parse = function (str) { 511 try { 512 return JSON.parse(str); 513 } 514 catch (e) { 515 return false; 516 } 517 }; 518 519 /** 520 * A simple wrapper to animate anything using animate.css. 521 * 522 * @name dBlazy.animate 523 * 524 * @param {Element} el 525 * The animated HTML element. 526 * @param {String} animation 527 * Any custom animation name, fallbacks to [data-animation]. 528 */ 529 dBlazy.animate = function (el, animation) { 530 var me = this; 531 var props = [ 532 'animation', 533 'animation-duration', 534 'animation-delay', 535 'animation-iteration-count' 536 ]; 537 538 animation = animation || el.dataset.animation; 539 el.classList.add('animated', animation); 540 me.forEach(['Duration', 'Delay', 'IterationCount'], function (key) { 541 if ('animation' + key in el.dataset) { 542 el.style['animation' + key] = el.dataset['animation' + key]; 543 } 544 }); 545 546 function animationEnd() { 547 me.removeAttrs(el, props); 548 549 el.classList.add('is-b-animated'); 550 el.classList.remove('animated', animation); 551 552 me.forEach(props, function (key) { 553 el.style.removeProperty(key); 554 }); 555 556 me.unbindEvent(el, 'animationend', animationEnd); 557 } 558 559 me.bindEvent(el, 'animationend', animationEnd); 560 }; 561 562 /** 563 * A simple wrapper to delay callback function, taken out of blazy library. 564 * 565 * Alternative to core Drupal.debounce for D7 compatibility, and easy port. 566 * 567 * @name dBlazy.throttle 568 * 569 * @param {Function} fn 570 * The callback function. 571 * @param {Int} minDelay 572 * The execution delay in milliseconds. 573 * @param {Object} scope 574 * The scope of the function to apply to, normally this. 575 * 576 * @return {Function} 577 * The function executed at the specified minDelay. 578 */ 579 dBlazy.throttle = function (fn, minDelay, scope) { 580 var lastCall = 0; 581 return function () { 582 var now = +new Date(); 583 if (now - lastCall < minDelay) { 584 return; 585 } 586 lastCall = now; 587 fn.apply(scope, arguments); 588 }; 589 }; 590 591 /** 592 * A simple wrapper to delay callback function on window resize. 593 * 594 * @name dBlazy.resize 595 * 596 * @link https://github.com/louisremi/jquery-smartresize 597 * 598 * @param {Function} c 599 * The callback function. 600 * @param {Int} t 601 * The timeout. 602 * 603 * @return {Function} 604 * The callback function. 605 */ 606 dBlazy.resize = function (c, t) { 607 window.onresize = function () { 608 window.clearTimeout(t); 609 t = window.setTimeout(c, 200); 610 }; 611 return c; 612 }; 613 614 /** 615 * A simple wrapper for triggering event like jQuery.trigger(). 616 * 617 * @name dBlazy.trigger 618 * 619 * @param {Element} elm 620 * The HTML element. 621 * @param {String} eventName 622 * The event name to trigger. 623 * @param {Object} custom 624 * The optional object passed into a custom event. 625 * @param {Object} param 626 * The optional param passed into a custom event. 627 * 628 * @see https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events 629 * @todo: See if any consistent way for both custom and native events. 630 */ 631 dBlazy.trigger = function (elm, eventName, custom, param) { 632 var event; 633 var data = { 634 detail: custom || {} 635 }; 636 637 if (typeof param === 'undefined') { 638 data.bubbles = true; 639 data.cancelable = true; 640 } 641 642 // Native. 643 // IE >= 9 compat, else SCRIPT445: Object doesn't support this action. 644 // https://msdn.microsoft.com/library/ff975299(v=vs.85).aspx 645 if (typeof window.CustomEvent === 'function') { 646 event = new CustomEvent(eventName, data); 647 } 648 else { 649 event = document.createEvent('CustomEvent'); 650 event.initCustomEvent(eventName, true, true, data); 651 } 652 653 elm.dispatchEvent(event); 654 }; 655 656 return dBlazy; 657 658});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.