docs: function defineImagesLoaded( EventEmitter, eventie ) {
  
    /**
     * @param {Array, Element, NodeList, String} elem
     * @param {Object or Function} options - if function, use as callback
     * @param {Function} onAlways - callback function
     */
    function ImagesLoaded( elem, options, onAlways ) {
      // coerce ImagesLoaded() without new, to be new ImagesLoaded()
      if ( !( this instanceof ImagesLoaded ) ) {
        return new ImagesLoaded( elem, options );
      }
      // use elem as selector string
      if ( typeof elem === 'string' ) {
        elem = document.querySelectorAll( elem );
      }
  
      this.elements = makeArray( elem );
      this.options = extend( {}, this.options );
  
      if ( typeof options === 'function' ) {
        onAlways = options;
      } else {
        extend( this.options, options );
      }
  
      if ( onAlways ) {
        this.on( 'always', onAlways );
      }
  
      this.getImages();
  
      if ( $ ) {
        // add jQuery Deferred object
        this.jqDeferred = new $.Deferred();
      }
  
      // HACK check async to allow time to bind listeners
      var _this = this;
      setTimeout( function() {
        _this.check();
      });
    }
  
    ImagesLoaded.prototype = new EventEmitter();
  
    ImagesLoaded.prototype.options = {};
  
    ImagesLoaded.prototype.getImages = function() {
      this.images = [];
  
      // filter & find items if we have an item selector
      for ( var i=0, len = this.elements.length; i < len; i++ ) {
        var elem = this.elements[i];
        // filter siblings
        if ( elem.nodeName === 'IMG' ) {
          this.addImage( elem );
        }
        // find children
        var childElems = elem.querySelectorAll('img');
        // concat childElems to filterFound array
        for ( var j=0, jLen = childElems.length; j < jLen; j++ ) {
          var img = childElems[j];
          this.addImage( img );
        }
      }
    };
  
    /**
     * @param {Image} img
     */
    ImagesLoaded.prototype.addImage = function( img ) {
      var loadingImage = new LoadingImage( img );
      this.images.push( loadingImage );
    };
  
    ImagesLoaded.prototype.check = function() {
      var _this = this;
      var checkedCount = 0;
      var length = this.images.length;
      this.hasAnyBroken = false;
      // complete if no images
      if ( !length ) {
        this.complete();
        return;
      }
  
      function onConfirm( image, message ) {
        if ( _this.options.debug && hasConsole ) {
          console.log( 'confirm', image, message );
        }
  
        _this.progress( image );
        checkedCount++;
        if ( checkedCount === length ) {
          _this.complete();
        }
        return true; // bind once
      }
  
      for ( var i=0; i < length; i++ ) {
        var loadingImage = this.images[i];
        loadingImage.on( 'confirm', onConfirm );
        loadingImage.check();
      }
    };
  
    ImagesLoaded.prototype.progress = function( image ) {
      this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded;
      // HACK - Chrome triggers event before object properties have changed. #83
      var _this = this;
      setTimeout( function() {
        _this.emit( 'progress', _this, image );
        if ( _this.jqDeferred ) {
          _this.jqDeferred.notify( _this, image );
        }
      });
    };
  
    ImagesLoaded.prototype.complete = function() {
      var eventName = this.hasAnyBroken ? 'fail' : 'done';
      this.isComplete = true;
      var _this = this;
      // HACK - another setTimeout so that confirm happens after progress
      setTimeout( function() {
        _this.emit( eventName, _this );
        _this.emit( 'always', _this );
        if ( _this.jqDeferred ) {
          var jqMethod = _this.hasAnyBroken ? 'reject' : 'resolve';
          _this.jqDeferred[ jqMethod ]( _this );
        }
      });
    };
  
    // -------------------------- jquery -------------------------- //
  
    if ( $ ) {
      $.fn.imagesLoaded = function( options, callback ) {
        var instance = new ImagesLoaded( this, options, callback );
        return instance.jqDeferred.promise( $(this) );
      };
    }
  
  
    // --------------------------  -------------------------- //
  
    function LoadingImage( img ) {
      this.img = img;
    }
  
    LoadingImage.prototype = new EventEmitter();
  
    LoadingImage.prototype.check = function() {
      // first check cached any previous images that have same src
      var resource = cache[ this.img.src ] || new Resource( this.img.src );
      if ( resource.isConfirmed ) {
        this.confirm( resource.isLoaded, 'cached was confirmed' );
        return;
      }
  
      // If complete is true and browser supports natural sizes,
      // try to check for image status manually.
      if ( this.img.complete && this.img.naturalWidth !== undefined ) {
        // report based on naturalWidth
        this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
        return;
      }
  
      // If none of the checks above matched, simulate loading on detached element.
      var _this = this;
      resource.on( 'confirm', function( resrc, message ) {
        _this.confirm( resrc.isLoaded, message );
        return true;
      });
  
      resource.check();
    };
  
    LoadingImage.prototype.confirm = function( isLoaded, message ) {
      this.isLoaded = isLoaded;
      this.emit( 'confirm', this, message );
    };
  
    // -------------------------- Resource -------------------------- //
  
    // Resource checks each src, only once
    // separate class from LoadingImage to prevent memory leaks. See #115
  
    var cache = {};
  
    function Resource( src ) {
      this.src = src;
      // add to cache
      cache[ src ] = this;
    }
  
    Resource.prototype = new EventEmitter();
  
    Resource.prototype.check = function() {
      // only trigger checking once
      if ( this.isChecked ) {
        return;
      }
      // simulate loading on detached element
      var proxyImage = new Image();
      eventie.bind( proxyImage, 'load', this );
      eventie.bind( proxyImage, 'error', this );
      proxyImage.src = this.src;
      // set flag
      this.isChecked = true;
    };
  
    // ----- events ----- //
  
    // trigger specified handler for event type
    Resource.prototype.handleEvent = function( event ) {
      var method = 'on' + event.type;
      if ( this[ method ] ) {
        this[ method ]( event );
      }
    };
  
    Resource.prototype.onload = function( event ) {
      this.confirm( true, 'onload' );
      this.unbindProxyEvents( event );
    };
  
    Resource.prototype.onerror = function( event ) {
      this.confirm( false, 'onerror' );
      this.unbindProxyEvents( event );
    };
  
    // ----- confirm ----- //
  
    Resource.prototype.confirm = function( isLoaded, message ) {
      this.isConfirmed = true;
      this.isLoaded = isLoaded;
      this.emit( 'confirm', this, message );
    };
  
    Resource.prototype.unbindProxyEvents = function( event ) {
      eventie.unbind( event.target, 'load', this );
      eventie.unbind( event.target, 'error', this );
    };
  
    // -----  ----- //
  
    return ImagesLoaded;
  }