Home Reference Source

scripts/utils/loader.js

/* globals XMLHttpRequest, Image, Audio */

'use strict'

import { loop } from './loop.js'
import { isObject } from './isobject.js'

/**
 * The Loader class is used to preload files
 */
class Loader {
  /**
   * Creates an instance of the Loader class
   */
  constructor () {
    /**
     * Contains a cache of files
     * @type {Object}
     * @private
     */
    this.__cache = {}
  }

  /**
   * Create a new instance of the Loader
   * @return {Loader} The new Loader instance
   */
  create () {
    return new Loader()
  }

  /**
   * Preload an image
   * @param {String} id A unique identifier for this file
   * @param {String} url The image url to load
   * @param {Image} image The image oject used to load the asset. If null a new one will be created.
   * @return {Promise} Returns a promise which is resolved upon loading
   */
  preloadImage ( id, url, image ) {
    let img = image || new Image()
    return new Promise( ( resolve, reject ) => {
      img.onload = () => {
        this.__cache[ id ] = img
        resolve( this.__cache[ id ] )
      }
      img.onerror = ( err ) => {
        reject( err )
      }
      img.src = url
    } )
  }

  /**
   * Preload an audio file
   * @param {String} id A unique identifier for this file
   * @param {String} url The audio url to load
   * @param {Audio} audio The audio oject used to load the asset. If null a new one will be created.
   * @return {Promise} Returns a promise which is resolved upon loading
   */
  preloadAudio ( id, url, audio ) {
    let sound = audio || new Audio()
    return new Promise( ( resolve, reject ) => {
      sound.addEventListener( 'canplay', () => {
        this.__cache[ id ] = sound
        resolve( this.__cache[ id ] )
      } )
      sound.onerror = ( err ) => {
        reject( err )
      }
      sound.src = url
    } )
  }

  /**
   * Preload a file using HTTPRequest
   * @param {String} id A unique identifier for this file
   * @param {String} url The file url to load
   * @param {XMLHttpRequest} request The xhr oject used to load the asset. If null a new one will be created.
   * @return {Promise} Returns a promise which is resolved upon loading
   */
  preloadFile ( id, url, request ) {
    let xhr = request || new XMLHttpRequest()
    return new Promise( ( resolve, reject ) => {
      xhr.open( 'GET', url )
      xhr.onload = () => {
        this.__cache[ id ] = xhr.responseText
        resolve( this.__cache[ id ] )
      }
      xhr.onerror = ( err ) => {
        reject( err )
      }
      xhr.send()
    } )
  }

  /**
   * Load a list of files
   * @param {Object} files Contains a list of files to load with the following structure: { "file1": "url1", "file2": "url2" } or { "file1": { type: "image|audio|file", url: "url1", obj: objectLoader|null } }
   * @param {Function} progress A callback function with the parameters: "err, id, data, loaded, total"
   * @return {Promise} Returns a promise which is resolved upon loading all files, doesn't matter if they failed or not
   */
  preload ( files, progress ) {
    return new Promise( ( resolve ) => {
      let total = Object.keys( files ).length
      let loaded = 0
      let index = 0

      let process = ( err, id, data, loaded, total ) => {
        if ( typeof progress === 'function' ) {
          progress( err, id, data, loaded, total )
        }
        index++
        if ( total === index ) {
          resolve( total - loaded )
        }
      }

      if ( total === 0 ) {
        resolve( 0 )
      }

      loop( files, ( file, id ) => {
        let type = 'file'
        let obj = null
        let url = file
        if ( isObject( file ) ) {
          type = file.type
          url = file.url
          obj = file.obj || null
        }
        let loader = this[ 'preload' + type.charAt( 0 ).toUpperCase() + type.slice( 1 ).toLowerCase() ]
        loader.call( this, id, url, obj )
          .then( ( data ) => {
            loaded++
            process( null, id, data, loaded, total )
          } )
          .catch( ( err ) => {
            process( err, id, null, loaded, total )
          } )
      } )
    } )
  }

  /**
   * Returns the cache containing the preloaded files
   * @return {Object}
   */
  getCache () {
    return this.__cache
  }

  /**
   * Returns a load file by id
   * @param {String} id
   * @return {Object}
   */
  get ( id ) {
    return this.__cache[ id ]
  }
}

/**
 * Returns a singleton of the Loader
 */
export let loader = new Loader()