├── api.md ├── .npmignore ├── test ├── test_data.json ├── big_buck_bunny.mp4 └── index.js ├── index.js ├── .gitignore ├── lib ├── util │ ├── isBase64.js │ ├── arrayBufferToString.js │ ├── stringToArrayBuffer.js │ ├── getExtension.js │ ├── parseHTTPHeader.js │ └── getMimeFromURL.js ├── loaders │ ├── LoaderText.js │ ├── LoaderBlob.js │ ├── LoaderArrayBuffer.js │ ├── LoaderJSON.js │ ├── LoaderAudio.js │ ├── LoaderVideo.js │ ├── FileMeta.js │ ├── LoaderImage.js │ └── LoaderBase.js └── index.js ├── LICENSE.md ├── package.json └── README.md /api.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | -------------------------------------------------------------------------------- /test/test_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true 3 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/index'); 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | -------------------------------------------------------------------------------- /test/big_buck_bunny.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Experience-Monks/preloader/HEAD/test/big_buck_bunny.mp4 -------------------------------------------------------------------------------- /lib/util/isBase64.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @reference https://github.com/miguelmota/is-base64/pull/2 3 | */ 4 | module.exports = function isBase64 (v) { 5 | var regex = /^(data:\w+\/[a-zA-Z\+\-\.]+;base64,)?([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$/gi; 6 | return regex.test(v); 7 | }; 8 | -------------------------------------------------------------------------------- /lib/util/arrayBufferToString.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This function will convert an Array Buffer to a String 3 | * 4 | * @method arrayBufferToString 5 | * @param {ArrayBuffer} buffer The ArrayBuffer we'd like to convert to a string 6 | * @return {String} The string representation of an ArrayBuffer 7 | */ 8 | module.exports = function (buffer) { 9 | return String.fromCharCode.apply(null, new Uint16Array(buffer)); 10 | }; 11 | -------------------------------------------------------------------------------- /lib/util/stringToArrayBuffer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This will convert a string to an ArrayBuffer 3 | * 4 | * @method stringToArrayBuffer 5 | * @param {String} string The string to convert to an array buffer 6 | * @return {ArrayBuffer} The string data which was converted into an ArrayBuffer 7 | */ 8 | module.exports = function (string) { 9 | var buf = new ArrayBuffer(string.length * 2); // 2 bytes for each char 10 | var bufView = new Uint16Array(buf); 11 | 12 | for (var i = 0, strLen = string.length; i < strLen; i++) { 13 | bufView[ i ] = string.charCodeAt(i); 14 | } 15 | 16 | return buf; 17 | }; 18 | -------------------------------------------------------------------------------- /lib/loaders/LoaderText.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This module will contain everything related to preloading. 3 | * 4 | * @module preloader 5 | */ 6 | var Class = require('js-oop'); 7 | var LoaderBase = require('./LoaderBase'); 8 | 9 | /** 10 | * LoaderText will load a file and the content saved in this Loader will be a String. 11 | * 12 | * @class LoaderText 13 | * @constructor 14 | * @extends {LoaderBase} 15 | */ 16 | var LoaderText = new Class({ 17 | Extends: LoaderBase, 18 | initialize: function (options) { 19 | Class.parent(this, LoaderBase.typeText, options); 20 | } 21 | }); 22 | 23 | module.exports = LoaderText; 24 | -------------------------------------------------------------------------------- /lib/loaders/LoaderBlob.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This module will contain everything related to preloading. 3 | * 4 | * @module preloader 5 | */ 6 | var Class = require('js-oop'); 7 | var LoaderBase = require('./LoaderBase'); 8 | 9 | var LoaderBlob = new Class({ 10 | Extends: LoaderBase, 11 | /** 12 | * LoaderBlob will load a file and the content saved in this Loader will be a Blob 13 | * 14 | * @class LoaderBlob 15 | * @constructor 16 | * @extends {LoaderBase} 17 | */ 18 | initialize: function (options) { 19 | Class.parent(this, LoaderBase.typeBlob, options); 20 | } 21 | }); 22 | 23 | module.exports = LoaderBlob; 24 | -------------------------------------------------------------------------------- /lib/loaders/LoaderArrayBuffer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This module will contain everything related to preloading. 3 | * 4 | * @module preloader 5 | */ 6 | var Class = require('js-oop'); 7 | var LoaderBase = require('./LoaderBase'); 8 | 9 | var LoaderArrayBuffer = new Class({ 10 | Extends: LoaderBase, 11 | /** 12 | * LoaderArrayBuffer will load a file and the content saved in this Loader will be an ArrayBuffer 13 | * 14 | * @class LoaderArrayBuffer 15 | * @constructor 16 | * @extends {LoaderBase} 17 | */ 18 | initialize: function (options) { 19 | Class.parent(this, LoaderBase.typeArraybuffer, options); 20 | } 21 | }); 22 | 23 | module.exports = LoaderArrayBuffer; 24 | -------------------------------------------------------------------------------- /lib/loaders/LoaderJSON.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This module will contain everything related to preloading. 3 | * 4 | * @module preloader 5 | */ 6 | var Class = require('js-oop'); 7 | var LoaderBase = require('./LoaderBase'); 8 | 9 | /** 10 | * LoaderJSON will load a JSON file and parse it's content. The content property will contain an Object 11 | * representation of the JSON data. 12 | * 13 | * @class LoaderJSON 14 | * @constructor 15 | * @extends {LoaderBase} 16 | */ 17 | var LoaderJSON = new Class({ 18 | Extends: LoaderBase, 19 | initialize: function (options) { 20 | Class.parent(this, LoaderBase.typeJSON, options); 21 | } 22 | }); 23 | 24 | module.exports = LoaderJSON; 25 | -------------------------------------------------------------------------------- /lib/util/getExtension.js: -------------------------------------------------------------------------------- 1 | var base64Mime = require('base64mime'); 2 | 3 | var isBase64 = require('./isBase64'); 4 | 5 | /** 6 | * Return the file extension based on the path passed in. If the file does not have an extension null will be passed back 7 | * 8 | * @method getExtension 9 | * @param {String} url URL we'd like a filextension from. This can be relative or absolute. 10 | * @return {String} 11 | */ 12 | module.exports = function getExtension (url) { 13 | var ext; 14 | 15 | if (isBase64(url)) { 16 | var mime = base64Mime(url); 17 | ext = (mime.split('/'))[1]; 18 | } else { 19 | ext = url.split('.').pop(); 20 | } 21 | 22 | return ext || null; 23 | }; 24 | -------------------------------------------------------------------------------- /lib/loaders/LoaderAudio.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This module will contain everything related to preloading. 3 | * 4 | * @module preloader 5 | */ 6 | var Class = require('js-oop'); 7 | var LoaderBase = require('./LoaderBase'); 8 | var LoaderVideo = require('./LoaderVideo'); 9 | 10 | /** 11 | * LoaderAudio will load an audio file. The content property will contain an audio tag. 12 | * 13 | * @class LoaderAudio 14 | * @constructor 15 | * @extends {LoaderVideo} 16 | */ 17 | var LoaderAudio = new Class({ 18 | Extends: LoaderVideo, 19 | initialize: function (options) { 20 | Class.parent(this, options); 21 | this.loadType = LoaderBase.typeAudio; 22 | } 23 | }); 24 | 25 | module.exports = LoaderAudio; 26 | -------------------------------------------------------------------------------- /lib/util/parseHTTPHeader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This function will take an HTTP header and turn it into an object for easier use. 3 | * 4 | * @method parseHTTPHeader 5 | * @param {String} headerString This is an HTTP header 6 | * @return {Object} The return value will be an object representation of the HTTP Header 7 | */ 8 | module.exports = function (headerString) { 9 | var headerSplit = headerString.split('\n'); 10 | var rVal = {}; 11 | var regex = /([a-zA-Z0-9\-_]+): *(.+)/; 12 | var keyValue = null; 13 | 14 | for (var i = 0, len = headerSplit.length; i < len; i++) { 15 | // the end has an extra newline 16 | if (headerSplit[ i ] !== '') { 17 | keyValue = regex.exec(headerSplit[ i ]); 18 | 19 | if (keyValue) { 20 | rVal[ keyValue[ 1 ] ] = keyValue[ 2 ]; 21 | } 22 | } 23 | } 24 | 25 | return rVal; 26 | }; 27 | -------------------------------------------------------------------------------- /lib/loaders/LoaderVideo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This module will contain everything related to preloading. 3 | * 4 | * @module preloader 5 | */ 6 | var Class = require('js-oop'); 7 | var LoaderBase = require('./LoaderBase'); 8 | 9 | /** 10 | * LoaderVideo will load a video file. The content property will contain a video tag. 11 | * 12 | * @class LoaderVideo 13 | * @constructor 14 | * @extends {LoaderBase} 15 | */ 16 | var LoaderVideo = new Class({ 17 | Extends: LoaderBase, 18 | initialize: function (options) { 19 | Class.parent(this, LoaderBase.typeVideo, options); 20 | }, 21 | 22 | _parseContent: function () { 23 | Class.parent(this); 24 | 25 | if (window.URL && window.URL.createObjectURL) { 26 | var blobURL = window.URL.createObjectURL(this.content); 27 | this.content = document.createElement(this.loadType); 28 | this.content.src = blobURL; 29 | } else { 30 | throw new Error('This browser does not support URL.createObjectURL()'); 31 | } 32 | } 33 | }); 34 | 35 | module.exports = LoaderVideo; 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2014 Mikko Haapoja 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 20 | OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "preloader", 3 | "version": "4.0.3", 4 | "description": "A library for loading common web assets", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "npm run lint && budo test/ --open --dir test/", 8 | "lint": "semistandard --fix" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git@github.com:Jam3/preloader.git" 13 | }, 14 | "keywords": [ 15 | "preload", 16 | "load", 17 | "json", 18 | "image", 19 | "binary", 20 | "loader", 21 | "preloader" 22 | ], 23 | "author": { 24 | "name": "Nick Poisson", 25 | "email": "nick@jam3.com", 26 | "url": "https://github.com/njam3" 27 | }, 28 | "contributors": [ 29 | { 30 | "name": "Mikko Haapoja", 31 | "email": "me@mikkoh.com", 32 | "url": "https://github.com/mikkoh" 33 | } 34 | ], 35 | "license": "MIT", 36 | "bugs": { 37 | "url": "https://github.com/Jam3/preloader/issues" 38 | }, 39 | "homepage": "https://github.com/Jam3/preloader", 40 | "dependencies": { 41 | "base64mime": "0.0.3", 42 | "js-oop": "^1.0.0" 43 | }, 44 | "devDependencies": { 45 | "budo": "9.2.1", 46 | "semistandard": "9.1.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/util/getMimeFromURL.js: -------------------------------------------------------------------------------- 1 | var base64Mime = require('base64mime'); 2 | 3 | var getExtension = require('./getExtension'); 4 | var isBase64 = require('./isBase64'); 5 | 6 | var FILE_MIME = { 7 | // images 8 | gif: 'image/gif', 9 | jpg: 'image/jpeg', 10 | jpeg: 'image/jpeg', 11 | png: 'image/png', 12 | svg: 'image/svg+xml', 13 | // text 14 | html: 'text/html', 15 | css: 'text/css', 16 | csv: 'text/csv', 17 | xml: 'text/xml', 18 | // video 19 | mp4: 'video/mp4', 20 | ogg: 'video/ogg', 21 | ogv: 'video/ogg', 22 | webm: 'video/webm', 23 | // audio 24 | wav: 'audio/wav', 25 | mp3: 'audio/mpeg' 26 | }; 27 | 28 | /** 29 | * This function will return a mime type based on a file extension or a url. For instance the file 'jpg' would return 30 | * 'image/jpeg'. 31 | * 32 | * @method getMimeFromURL 33 | * @param {String} type File extension 34 | * @return {String} Mime type 35 | */ 36 | module.exports = function getMimeFromURL (url) { 37 | var mime; 38 | 39 | if (isBase64(url)) { 40 | mime = base64Mime(url); 41 | } else { 42 | var ext = getExtension(url); 43 | mime = FILE_MIME[ ext.toLowerCase() ]; 44 | } 45 | 46 | return mime || 'application/octet-stream'; 47 | }; 48 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var preloader = require('../'); 2 | var loader = preloader({ 3 | xhrImages: false, 4 | loadFullAudio: false, 5 | loadFullVideo: false 6 | }); 7 | loader.on('progress', function (progress) { 8 | console.log(progress); 9 | }); 10 | loader.on('complete', function () { 11 | var data = loader.get('test_data.json'); 12 | console.log('all content loaded:', data.success); 13 | }); 14 | loader.add('big_buck_bunny.mp4', { 15 | onComplete: function (content) { 16 | var video = loader.get('big_buck_bunny.mp4'); 17 | video.setAttribute('controls', true); 18 | document.body.appendChild(video); 19 | } 20 | }); 21 | loader.add('http://sandbox.thewikies.com/vfe-generator/images/big-buck-bunny_poster.jpg', { 22 | onComplete: function (content) { 23 | document.body.appendChild(content); 24 | } 25 | }); 26 | loader.add('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEACAhITMkM1EwMFFCLy8vQiccHBwcJyIXFxcXFyIRDAwMDAwMEQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwBIjMzNCY0IhgYIhQODg4UFA4ODg4UEQwMDAwMEREMDAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDP/AABEIAAYABgMBIgACEQEDEQH/xABVAAEBAAAAAAAAAAAAAAAAAAAAAxAAAQQCAwEAAAAAAAAAAAAAAgABAxQEIxIkMxMBAQAAAAAAAAAAAAAAAAAAAAARAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AIE7MwkbOUJDJWx+ZjXATitx2/h2bEWvX5Y0npQ7aIiD/9k=', { 27 | onComplete: function (content) { 28 | document.body.appendChild(content); 29 | } 30 | }); 31 | loader.add('test_data.json'); 32 | loader.load(); 33 | -------------------------------------------------------------------------------- /lib/loaders/FileMeta.js: -------------------------------------------------------------------------------- 1 | var parseHTTPHeader = require('../util/parseHTTPHeader'); 2 | 3 | /** 4 | * FileMeta is a class which will hold file meta data. Each LoaderBase contains a FileMeta object 5 | * that you can use to query. 6 | * 7 | * @class FileMeta 8 | * @constructor 9 | * @param {String} header HTTP Header sent when loading this file 10 | */ 11 | var FileMeta = function (header) { 12 | /** 13 | * This property is the mimetype for the file 14 | * 15 | * @property mime 16 | * @type {String} 17 | */ 18 | this.mime = null; 19 | 20 | /** 21 | * This is the file size in bytes 22 | * 23 | * @type {Number} 24 | */ 25 | this.size = null; 26 | 27 | /** 28 | * This is a Date object which represents the last time this file was modified 29 | * 30 | * @type {Date} 31 | */ 32 | this.lastModified = null; 33 | 34 | /** 35 | * This is the HTTP header as an Object for the file. 36 | * 37 | * @type {Object} 38 | */ 39 | this.httpHeader = null; 40 | 41 | if (header) this.setFromHTTPHeader(header); 42 | }; 43 | 44 | FileMeta.prototype = { 45 | 46 | /** 47 | * This function will be called in the constructor of FileMeta. It will parse the HTTP 48 | * headers returned by a server and save useful information for development. 49 | * 50 | * @method setFromHTTPHeader 51 | * @param {String} header HTTP header returned by the server 52 | */ 53 | setFromHTTPHeader: function (header) { 54 | this.httpHeader = parseHTTPHeader(header); 55 | 56 | if (this.httpHeader[ 'content-length' ]) this.size = this.httpHeader[ 'content-length' ]; 57 | 58 | if (this.httpHeader[ 'content-type' ]) this.mime = this.httpHeader[ 'content-type' ]; 59 | 60 | if (this.httpHeader[ 'last-modified' ]) this.lastModified = new Date(this.httpHeader[ 'last-modified' ]); 61 | } 62 | }; 63 | 64 | module.exports = FileMeta; 65 | -------------------------------------------------------------------------------- /lib/loaders/LoaderImage.js: -------------------------------------------------------------------------------- 1 | /* global Blob, Image, ArrayBuffer, FileReader */ 2 | 3 | /** 4 | * This module will contain everything related to preloading. 5 | * 6 | * @module preloader 7 | */ 8 | var Class = require('js-oop'); 9 | var LoaderBase = require('./LoaderBase'); 10 | var FileMeta = require('./FileMeta'); 11 | var getMimeFromURL = require('./../util/getMimeFromURL'); 12 | 13 | /** 14 | * LoaderImage will load in images. If XHR exists in the browser attempting to load image 15 | * then XHR will be used otherwise LoaderImage will use Image instead to load the Image. 16 | * 17 | * @class LoaderImage 18 | * @constructor 19 | * @extends {LoaderBase} 20 | */ 21 | var LoaderImage = new Class({ 22 | 23 | Extends: LoaderBase, 24 | 25 | initialize: function (options) { 26 | this._imageLoaded = false; 27 | Class.parent(this, LoaderBase.typeArraybuffer, options); 28 | }, 29 | 30 | load: function (url) { 31 | // first we check if we can load with XHR period 32 | // second check that we can load using the method we'd like to which is ArrayBuffer 33 | // third we check that we have all the functions to turn an ArrayBuffer to a DataURI 34 | if (this.options.xhrImages && 35 | this.canLoadUsingXHR() && 36 | this.canLoadType(this.loadType) && 37 | ArrayBuffer && (window.URL || window.webkitURL || FileReader)) { 38 | Class.parent(this, url); 39 | // if the above checks dont validate we'll fall back and just use the Image object to preload 40 | } else { 41 | this._createAndLoadImage(url); 42 | } 43 | }, 44 | 45 | _dispatchProgress: function (progress) { 46 | Class.parent(this, this._imageLoaded ? progress : progress * 0.9999); 47 | }, 48 | 49 | _dispatchComplete: function () { 50 | if (this._imageLoaded) Class.parent(this); 51 | }, 52 | 53 | _onImageLoadComplete: function () { 54 | this._imageLoaded = true; 55 | this._dispatchProgress(1); 56 | this._dispatchComplete(); 57 | }, 58 | 59 | _onImageLoadFail: function () { 60 | this._dispatchError('Image failed to load'); 61 | }, 62 | 63 | _parseContent: function () { 64 | var arrayBuffer = null; 65 | var blobData = null; 66 | 67 | if (!this.fileMeta) { 68 | this.fileMeta = new FileMeta(); 69 | } 70 | 71 | // if the loadType was not set then the meta will be incorrect possibly 72 | // so we'll read it from the url 73 | if (!this.loadTypeSet || this.fileMeta.mime === null) { 74 | this.fileMeta.mime = getMimeFromURL(this.url); 75 | } 76 | 77 | // get the ArrayBuffer 78 | if (this.xhr.response instanceof ArrayBuffer) { 79 | arrayBuffer = this.xhr.response; 80 | // if theres a property mozResponseArrayBuffer use that 81 | } else if (this.xhr.mozResponseArrayBuffer) { 82 | arrayBuffer = this.xhr.mozResponseArrayBuffer; 83 | // otherwise try converting the string to an ArrayBuffer 84 | } else { 85 | throw new Error('Return type for image load unsupported'); 86 | } 87 | 88 | blobData = new Blob([ arrayBuffer ], { type: this.fileMeta.mime }); 89 | 90 | // We'll convert the blob to an Image using FileReader if it exists 91 | if (window.URL || window.webkitURL) { 92 | this._createAndLoadImage((window.URL || window.webkitURL).createObjectURL(blobData)); 93 | } else if (FileReader) { 94 | var reader = new FileReader(); 95 | 96 | reader.onloadend = function () { 97 | if (window.URL || window.webkitURL) { 98 | (window.URL || window.webkitURL).revokeObjectURL(blobData); 99 | } 100 | 101 | this._createAndLoadImage(reader.result); 102 | }.bind(this); 103 | 104 | reader.readAsDataURL(blobData); 105 | } 106 | }, 107 | 108 | _createAndLoadImage: function (src) { 109 | this.content = new Image(); 110 | this.content.onload = this._onImageLoadComplete.bind(this); 111 | this.content.onerror = this._onImageLoadFail.bind(this); 112 | this.content.src = src; 113 | } 114 | }); 115 | 116 | module.exports = LoaderImage; 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Preloader 2 | 3 | [![experimental](http://badges.github.io/stability-badges/dist/experimental.svg)](http://github.com/badges/stability-badges) 4 | 5 | A library for loading common web assets 6 | 7 | ## Usage 8 | 9 | [![NPM](https://nodei.co/npm/preloader.png)](https://www.npmjs.com/package/preloader) 10 | 11 | # preloader 12 | 13 | The preloader is capable of loading almost all types of files, if it does not understand a file type, it will attempt to load it as a basic xhr request. It extends the [nodejs event emitter](https://nodejs.org/api/events.html) and uses the following events. 14 | 15 | ```progress```: `Event` Sends updates on loading progress to other part of application (loading ui) 16 | ```complete```: `Event` Notifies loading completion to other part of application 17 | 18 | Here is a common usage of the preloader. 19 | 20 | ```js 21 | var preloader = require('preloader'); 22 | var loader = preloader({ 23 | xhrImages: false 24 | }); 25 | loader.on('progress',function(progress) { 26 | console.log(progress); 27 | }); 28 | loader.on('complete',function() { 29 | var data = loader.get('site_data.json'); 30 | console.log('all content loaded!'); 31 | }); 32 | loader.add('video1.mp4'); 33 | loader.add('test_image.jpg',{ 34 | onComplete: function(content) { 35 | document.body.appendChild(loader.get('test_image.jpg')); 36 | } 37 | }); 38 | loader.add('site_data.json'); 39 | loader.load(); 40 | ``` 41 | 42 | ### new Preloader(options) / preloader(options) 43 | 44 | This creates a new instance of the preloader on which on you use the following api. It is not a singleton and must be instantiated to use. The options object contains the following properties. 45 | 46 | ```xhrImages``` Loads images via XHR and converts to a Blob instead of the image tag, default: false 47 | ```onComplete``` A function to attach to the complete event 48 | ```onProgress``` A function to attach to the progress event 49 | ```throttle``` A integer specifying maximum amount of connections at a time, 0 = infinite 50 | 51 | ### add(url, options) 52 | 53 | Generic asset loader function - determines loader to be used based on file-extension 54 | 55 | ```url```: `String` URL of asset 56 | ```options```: `Object` Custom options to override the global options created at instantiation, can also pass in `onComplete` and `onProgress` to listen to the events on this particular item. 57 | 58 | ### addImage(url ,options) 59 | 60 | Load image - uses the LoaderImage loader 61 | 62 | ```url```: `String` URL of asset 63 | ```options```: `Object` Custom options to override the global options created at instantiation, can also pass in `onComplete` and `onProgress` to listen to the events on this particular item. 64 | 65 | ### addJSON(url, options) 66 | 67 | Load JSON - uses the LoaderJSON loader 68 | 69 | ```url```: `String` URL of asset 70 | ```options```: `Object` Custom options to override the global options created at instantiation, can also pass in `onComplete` and `onProgress` to listen to the events on this particular item. 71 | 72 | ### addText(url, options) 73 | 74 | Load text - uses the LoaderText loader 75 | 76 | ```url```: `String` URL of asset 77 | ```options```: `Object` Custom options to override the global options created at instantiation, can also pass in `onComplete` and `onProgress` to listen to the events on this particular item. 78 | 79 | ### addVideo(url, options) 80 | 81 | Load video - uses the LoaderVideo loader 82 | 83 | ```url```: `String` URL of asset 84 | ```options```: `Object` Custom options to override the global options created at instantiation, can also pass in `onComplete` and `onProgress` to listen to the events on this particular item. 85 | 86 | ### addAudio(url, options) 87 | 88 | Load audio - uses the LoaderAudio loader 89 | 90 | ```url```: `String` URL of asset 91 | ```options```: `Object` Custom options to override the global options created at instantiation, can also pass in `onComplete` and `onProgress` to listen to the events on this particular item. 92 | 93 | ### addImage(url, options) 94 | 95 | Load image - uses the LoaderImage loader 96 | 97 | ```url```: `String` URL of asset 98 | ```options```: `Object` Custom options to override the global options created at instantiation, can also pass in `onComplete` and `onProgress` to listen to the events on this particular item. 99 | 100 | ### addFromLoaderType(url, loaderType, options) 101 | 102 | Load asset using custom loader 103 | 104 | ```url```: `String` URL of asset 105 | ```loaderType```: `function` Custom loader function 106 | ```options```: `Object` Custom options to override the global options created at instantiation, can also pass in `onComplete` and `onProgress` to listen to the events on this particular item. 107 | 108 | ### setPercentage(url, percentageOfLoad) 109 | 110 | Sets percentage of total load for a given asset 111 | 112 | ```url```: `String` URL of asset 113 | ```percentageOfLoad```: `Number` representing percentage of total load 114 | 115 | ### load() 116 | 117 | Begins loading process 118 | 119 | ### stopLoad() 120 | 121 | Stops loading process 122 | 123 | ### get(url) 124 | 125 | Retrieves loaded asset from loader 126 | 127 | ```url```: `String` URL of asset 128 | ```Returns```: asset instance 129 | 130 | ### reset() 131 | 132 | Resets loading so you can reuse the preloader. does not remove cached loads so `get()` continues to function for all assets. 133 | 134 | ## License 135 | 136 | MIT, see [LICENSE.md](https://github.com/Jam3/preloader/blob/master/LICENSE.md) for details. -------------------------------------------------------------------------------- /lib/loaders/LoaderBase.js: -------------------------------------------------------------------------------- 1 | /* global XMLHttpRequest, ArrayBuffer, Blob */ 2 | 3 | /** 4 | * This module will contain everything related to preloading. 5 | * 6 | * @module preloader 7 | */ 8 | var Class = require('js-oop'); 9 | var FileMeta = require('./FileMeta'); 10 | var stringToArrayBuffer = require('../util/stringToArrayBuffer'); 11 | var getMimeFromURL = require('../util/getMimeFromURL'); 12 | var EventEmitter = require('events').EventEmitter; 13 | 14 | var LoaderBase = new Class({ 15 | Extends: EventEmitter, 16 | /** 17 | * LoaderBase is the base class for all Preloader's. It wraps XHR nicely with Signal's as it's event system 18 | * also it should be able to handle working with: text, JSON, ArrayBuffer, Blob, and Document data out of the 19 | * box. (data XHR2 is able to handle) 20 | * 21 | * @class LoaderBase 22 | * @constructor 23 | */ 24 | initialize: function (loadType, options) { 25 | Class.parent(this); 26 | this.options = options; 27 | if (this.options.onComplete) this.on('complete', this.options.onComplete); 28 | if (this.options.onProgress) this.on('progress', this.options.onProgress); 29 | this.xhr = null; 30 | this.content = null; 31 | this.url = null; 32 | this.loadType = loadType || LoaderBase.typeText; 33 | this.loadTypeSet = false; 34 | this.fileMeta = null; 35 | 36 | this._onStateChange = this._onStateChange.bind(this); 37 | this._onProgress = this._onProgress.bind(this); 38 | this._dispatchProgress = this._dispatchProgress.bind(this); 39 | this._dispatchComplete = this._dispatchComplete.bind(this); 40 | }, 41 | 42 | /** 43 | * Call this method to find out if we can load data using XHR. This maybe useful for an Image loader for instance 44 | * if XHR can't be used then we can load the content using Image instead. 45 | * 46 | * @method canLoadUsingXHR 47 | * @return {[type]} [description] 48 | */ 49 | canLoadUsingXHR: function () { 50 | return typeof XMLHttpRequest !== 'undefined'; 51 | }, 52 | 53 | canLoadType: function (type) { 54 | var tempXHR = new XMLHttpRequest(); 55 | 56 | // need to open for ff so it doesn't fail 57 | tempXHR.open('GET', 'someFakeURL', true); 58 | 59 | return checkAndSetType(tempXHR, type); 60 | }, 61 | 62 | /** 63 | * The load function should be called to start preloading data. 64 | * 65 | * 66 | * The first parameter passed to the load function is the url to the data to be loaded. 67 | * It should be noted that mimetype for binary Blob data is read from 68 | * the file extension. EG. jpg will use the mimetype "image/jpeg". 69 | * 70 | * 71 | * @method load 72 | * @param {String} url This is the url to the data to be loaded 73 | */ 74 | load: function (url) { 75 | this.url = url; 76 | 77 | if (this.canLoadUsingXHR()) { 78 | this.xhr = new XMLHttpRequest(); 79 | this.xhr.open('GET', url, true); 80 | 81 | this.xhr.onreadystatechange = this._onStateChange; 82 | this.xhr.onprogress !== undefined && (this.xhr.onprogress = this._onProgress); 83 | 84 | if (this.loadType !== LoaderBase.typeText) { 85 | if (!checkIfGoodValue.call(this)) { 86 | console.warn('Attempting to use incompatible load type ' + this.loadType + '. Switching it to ' + LoaderBase.typeText); 87 | this.loadType = LoaderBase.typeText; 88 | } 89 | 90 | try { 91 | this.loadTypeSet = checkResponseTypeSupport.call(this) && checkAndSetType(this.xhr, this.loadType); 92 | } catch (e) { 93 | this.loadTypeSet = false; 94 | } 95 | 96 | if (!this.loadTypeSet && (this.loadType === LoaderBase.typeBlob || this.loadType === LoaderBase.typeArraybuffer)) { 97 | this.xhr.overrideMimeType('text/plain; charset=x-user-defined'); 98 | } 99 | } 100 | 101 | this.xhr.send(); 102 | } 103 | }, 104 | 105 | /** 106 | * Call this function to stop loading the asset which is currently being loaded. 107 | * 108 | * @method stopLoad 109 | */ 110 | stopLoad: function () { 111 | this.xhr.abort(); 112 | }, 113 | 114 | /** 115 | * When this function is called it will simply dispatch onStart. It maybe useful for classes 116 | * which extend LoaderBase to override this function. 117 | * 118 | * @method _dispatchStart 119 | * @protected 120 | */ 121 | _dispatchStart: function () { 122 | this.emit('start'); 123 | }, 124 | 125 | /** 126 | * When this function is called it will simply dispatch onProgress. It maybe useful for classes 127 | * which extend LoaderBase to override this function. 128 | * 129 | * @method _dispatchProgress 130 | * @protected 131 | * @param {Number} value This is a value between 0-1 which is the percentage of the files load 132 | */ 133 | _dispatchProgress: function (value) { 134 | this.emit('progress', value); 135 | }, 136 | 137 | /** 138 | * When this function is called it will simply dispatch onComplete. It maybe useful for classes 139 | * which extend LoaderBase to override this function. 140 | * 141 | * @method _dispatchComplete 142 | * @protected 143 | */ 144 | _dispatchComplete: function () { 145 | this.emit('complete', this.content); 146 | }, 147 | 148 | /** 149 | * When this function is called it will simply dispatch onError. It maybe useful for classes 150 | * which extend LoaderBase to override this function. 151 | * 152 | * @method _dispatchError 153 | * @protected 154 | * @param {String} msg The error message we'll be dispatching 155 | */ 156 | _dispatchError: function (msg) { 157 | this.emit('error', msg); 158 | }, 159 | 160 | /** 161 | * This callback will be called when the XHR progresses in its load. 162 | * 163 | * @method _onProgress 164 | * @protected 165 | * @param {XMLHttpRequestProgressEvent} ev This event contains data for the progress of the load 166 | */ 167 | _onProgress: function (ev) { 168 | var loaded = ev.loaded || ev.position; 169 | var totalSize = ev.total || ev.totalSize; 170 | 171 | if (totalSize) { 172 | this._dispatchProgress(loaded / totalSize); 173 | } else { 174 | this._dispatchProgress(0); 175 | } 176 | }, 177 | 178 | /** 179 | * This function is called whenever the readyState of the XHR object changes. 180 | * 181 | * this.xhr.readyState == 2 //send() has been called, and headers and status are available 182 | * this.xhr.readyState == 3 //Downloading; responseText holds partial data. 183 | * this.xhr.readyState == 4 //Done 184 | * 185 | * You should also handle HTTP error status codes: 186 | * 187 | * this.xhr.status == 404 //file doesn't exist 188 | * 189 | * @method _onStateChange 190 | * @protected 191 | */ 192 | _onStateChange: function () { 193 | if (this.xhr.readyState > 1) { 194 | var status; 195 | var waiting = false; 196 | // Fix error in IE8 where status isn't available until readyState=4 197 | try { status = this.xhr.status; } catch (e) { waiting = true; } 198 | 199 | if (status === 200) { 200 | switch (this.xhr.readyState) { 201 | 202 | // send() has been called, and headers and status are available 203 | case 2: 204 | 205 | this.fileMeta = new FileMeta(this.xhr.getAllResponseHeaders()); 206 | 207 | this._dispatchStart(); 208 | break; 209 | 210 | // Downloading; responseText holds partial data. 211 | case 3: 212 | 213 | // todo progress could be calculated here if onprogress does not exist on XHR 214 | // this.onProgress.dispatch(); 215 | break; 216 | 217 | // Done 218 | case 4: 219 | 220 | this._parseContent(); 221 | 222 | this._dispatchComplete(); 223 | break; 224 | } 225 | } else if (!waiting) { 226 | this.xhr.onreadystatechange = undefined; 227 | this._dispatchError(this.xhr.status); 228 | } 229 | } 230 | }, 231 | 232 | /** 233 | * This function will grab the response from the content loaded and parse it out 234 | * 235 | * @method _parseContent 236 | * @protected 237 | */ 238 | _parseContent: function () { 239 | if (this.loadTypeSet || this.loadType === LoaderBase.typeText) { 240 | this.content = this.xhr.response || this.xhr.responseText; 241 | } else { 242 | switch (this.loadType) { 243 | 244 | case LoaderBase.typeArraybuffer: 245 | 246 | if (ArrayBuffer) { 247 | this.content = stringToArrayBuffer(this.xhr.response); 248 | } else { 249 | throw new Error('This browser does not support ArrayBuffer'); 250 | } 251 | break; 252 | 253 | case LoaderBase.typeBlob: 254 | case LoaderBase.typeVideo: 255 | case LoaderBase.typeAudio: 256 | 257 | if (Blob) { 258 | if (!this.fileMeta) { 259 | this.fileMeta = new FileMeta(); 260 | } 261 | 262 | if (this.fileMeta.mime === null) { 263 | this.fileMeta.mime = getMimeFromURL(this.url); 264 | } 265 | 266 | this.content = new Blob([ stringToArrayBuffer(this.xhr.response) ], { type: this.fileMeta.mime }); 267 | } else { 268 | throw new Error('This browser does not support Blob'); 269 | } 270 | break; 271 | 272 | case LoaderBase.typeJSON: 273 | 274 | this.content = JSON.parse(this.xhr.response); 275 | break; 276 | 277 | case LoaderBase.typeDocument: 278 | 279 | // this needs some work pretty sure there's a better way to handle this 280 | this.content = this.xhr.response; 281 | break; 282 | 283 | } 284 | } 285 | } 286 | }); 287 | 288 | function checkIfGoodValue () { 289 | return this.loadType === LoaderBase.typeText || 290 | this.loadType === LoaderBase.typeArraybuffer || 291 | this.loadType === LoaderBase.typeBlob || 292 | this.loadType === LoaderBase.typeJSON || 293 | this.loadType === LoaderBase.typeDocument || 294 | this.loadType === LoaderBase.typeVideo || 295 | this.loadType === LoaderBase.typeAudio; 296 | } 297 | 298 | function checkResponseTypeSupport () { 299 | return this.xhr.responseType !== undefined; 300 | } 301 | 302 | function checkAndSetType (xhr, loadType) { 303 | if (loadType === LoaderBase.typeVideo || loadType === LoaderBase.typeAudio) { 304 | loadType = LoaderBase.typeBlob; 305 | } 306 | 307 | xhr.responseType = loadType; 308 | 309 | return xhr.responseType === loadType; 310 | } 311 | 312 | LoaderBase.typeText = 'text'; 313 | LoaderBase.typeArraybuffer = 'arraybuffer'; 314 | LoaderBase.typeBlob = 'blob'; 315 | LoaderBase.typeJSON = 'json'; 316 | LoaderBase.typeDocument = 'document'; 317 | LoaderBase.typeVideo = 'video'; 318 | LoaderBase.typeAudio = 'audio'; 319 | 320 | module.exports = LoaderBase; 321 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This module will contain everything related to preloading. 3 | * 4 | * @module preloader 5 | * 6 | */ 7 | 8 | var Class = require('js-oop'); 9 | var EventEmitter = require('events').EventEmitter; 10 | var getExtension = require('./util/getExtension'); 11 | var LoaderImage = require('./loaders/LoaderImage'); 12 | var LoaderText = require('./loaders/LoaderText'); 13 | var LoaderJSON = require('./loaders/LoaderJSON'); 14 | var LoaderVideo = require('./loaders/LoaderVideo'); 15 | var LoaderAudio = require('./loaders/LoaderAudio'); 16 | 17 | /** 18 | * 19 | * Object defining which file extensions use which loaders 20 | * 21 | * @property LOADERS 22 | * @type {Object} 23 | */ 24 | 25 | var LOADERS = { 26 | png: LoaderImage, 27 | jpg: LoaderImage, 28 | jpeg: LoaderImage, 29 | webp: LoaderImage, 30 | gif: LoaderImage, 31 | json: LoaderJSON, 32 | mp4: LoaderVideo, 33 | ogg: LoaderVideo, 34 | ogv: LoaderVideo, 35 | webm: LoaderVideo, 36 | mp3: LoaderAudio, 37 | wav: LoaderAudio 38 | }; 39 | 40 | /** 41 | * 42 | * Defines default loader 43 | * 44 | * @property LOADER_DEFAULT 45 | * @type {Function} 46 | */ 47 | var LOADER_DEFAULT = LoaderText; 48 | 49 | /** 50 | * 51 | * 52 | * @class Preloader 53 | * @constructor 54 | * @return {Object} Preloader Preloader object 55 | */ 56 | 57 | var Preloader = new Class({ 58 | Extends: EventEmitter, 59 | /** 60 | * 61 | * Called on instantiation, sets up properties of Preloader object 62 | * 63 | * @method initialize 64 | * 65 | */ 66 | 67 | initialize: function (options) { 68 | if (!(this instanceof Preloader)) return new Preloader(options); 69 | Class.parent(this); 70 | this.options = this.parseOptions(options); 71 | if (this.options.onComplete) this.on('complete', this.options.onComplete); 72 | if (this.options.onProgress) this.on('progress', this.options.onProgress); 73 | this.reset(); 74 | this.loaders = {}; 75 | 76 | this._continueLoadQueue = this._continueLoadQueue.bind(this); 77 | }, 78 | 79 | parseOptions: function (options) { 80 | return { 81 | xhrImages: options.xhrImages || false, 82 | onComplete: typeof options.onComplete === 'function' ? options.onComplete : undefined, 83 | onProgress: typeof options.onProgress === 'function' ? options.onProgress : undefined, 84 | throttle: options.throttle || 0 85 | }; 86 | }, 87 | 88 | mergeOptions: function (options) { 89 | return { 90 | xhrImages: options.xhrImages || this.options.xhrImages, 91 | onComplete: typeof options.onComplete === 'function' ? options.onComplete : this.options.onComplete, 92 | onProgress: typeof options.onProgress === 'function' ? options.onProgress : this.options.onProgress, 93 | throttle: options.throttle || this.options.throttle 94 | }; 95 | }, 96 | 97 | /** 98 | * 99 | * Generic asset loader function - determines loader to be used based on file-extension 100 | * 101 | * @method add 102 | * @param {String} url Base URL of asset 103 | * 104 | */ 105 | add: function (url, options) { 106 | if (url) { 107 | this.addFromLoaderType(url, this._getLoader(url), options); 108 | } 109 | }, 110 | 111 | /** 112 | * 113 | *Load image - uses the LoaderImage loader 114 | * 115 | * @method addImage 116 | * @param {String} url Base URL of asset 117 | * 118 | */ 119 | addImage: function (url, options) { 120 | this.addFromLoaderType(url, LoaderImage, options); 121 | }, 122 | 123 | /** 124 | * 125 | *Load JSON - uses the LoaderJSON loader 126 | * 127 | * @method addJSON 128 | * @param {String} url Base URL of asset 129 | * 130 | */ 131 | addJSON: function (url, options) { 132 | this.addFromLoaderType(url, LoaderJSON, options); 133 | }, 134 | 135 | /** 136 | * 137 | * Load text - uses the LoaderText loader 138 | * 139 | * @method addText 140 | * @param {String} url Base URL of asset 141 | * 142 | */ 143 | addText: function (url, options) { 144 | this.addFromLoaderType(url, LoaderText, options); 145 | }, 146 | 147 | /** 148 | * 149 | *Load video - uses the LoaderVideo loader 150 | * 151 | * @method addVideo 152 | * @param {String} url Base URL of asset 153 | * 154 | */ 155 | addVideo: function (url, options) { 156 | this.addFromLoaderType(url, LoaderVideo, options); 157 | }, 158 | 159 | /** 160 | * 161 | *Load audio - uses the LoaderAudio loader 162 | * 163 | * @method addAudio 164 | * @param {String} url Base URL of asset 165 | * 166 | */ 167 | addAudio: function (url, options) { 168 | this.addFromLoaderType(url, LoaderAudio, options); 169 | }, 170 | 171 | /** 172 | * 173 | * Load asset using custom loader 174 | * 175 | * @method addFromLoaderType 176 | * @param {String} url Base URL of asset 177 | * @param {Function} loaderType Custom loader function 178 | * 179 | */ 180 | addFromLoaderType: function (url, LoaderType, options) { 181 | if (!this.loaders[ url ]) { 182 | this.loaders[ url ] = new LoaderType(this.mergeOptions(options || {})); 183 | this.urls.push(url); 184 | return this.loaders[ url ]; 185 | } 186 | }, 187 | 188 | /** 189 | * 190 | * Sets percentage of total load for a given asset 191 | * 192 | * @method setPercentage 193 | * @param {String} url Base URL of asset 194 | * @param {Number} percentageOfLoad Number <= 1 representing percentage of total load 195 | * 196 | */ 197 | setPercentage: function (url, percentageOfLoad) { 198 | this.percentageOfLoad[ url ] = percentageOfLoad; 199 | }, 200 | 201 | /** 202 | * 203 | * Begins loading process 204 | * 205 | * @method load 206 | * 207 | */ 208 | load: function () { 209 | if (!this.loading) { 210 | this._setupPercentages(); 211 | var len = this.options.throttle || this.urls.length; 212 | for (var i = 0; i < len; i++) { 213 | this._continueLoadQueue(); 214 | } 215 | } 216 | }, 217 | 218 | /** 219 | * 220 | * Resets loading so you can reuse the preloader. does not remove cached loads so `get()` continues to function for all assets. 221 | * 222 | * @method reset 223 | * 224 | */ 225 | reset: function () { 226 | this.percTotal = 0; 227 | this.loadIdx = 0; 228 | this.urls = []; 229 | this.progress = 0; 230 | this.percentageOfLoad = {}; 231 | this.loading = false; 232 | this.status = {}; 233 | }, 234 | 235 | /** 236 | * 237 | * Stops loading process 238 | * 239 | * @method stopLoad 240 | * 241 | */ 242 | stopLoad: function () { 243 | if (this.loading) { 244 | for (var i = 0, len = this.urls.length; i < len; i++) { 245 | this.loaders[ this.urls[ i ] ].stopLoad(); 246 | } 247 | } 248 | }, 249 | 250 | /** 251 | * 252 | * Retrieves loaded asset from loader 253 | * 254 | * @method get 255 | * @param {String} url Base URL of asset 256 | * @return asset instance 257 | */ 258 | get: function (url) { 259 | return this.loaders[ url ] && this.loaders[ url ].content; 260 | }, 261 | 262 | /** 263 | * 264 | * Loops through stated percentages of all assets and standardizes them 265 | * 266 | * @method _setupPercentages 267 | */ 268 | _setupPercentages: function () { 269 | var percTotal = 0; 270 | var percScale = 1; 271 | // var numWPerc = 0 272 | var numWOPerc = 0; 273 | var oneFilePerc = 1 / this.urls.length; 274 | 275 | for (var i = 0, len = this.urls.length; i < len; i++) { 276 | if (this.percentageOfLoad[ this.urls[ i ] ]) { 277 | percTotal += this.percentageOfLoad[ this.urls[ i ] ]; 278 | // numWPerc++ 279 | } else { 280 | numWOPerc++; 281 | } 282 | } 283 | 284 | if (numWOPerc > 0) { 285 | if (percTotal > 1) { 286 | percScale = 1 / percTotal; 287 | percTotal *= percScale; 288 | } 289 | 290 | // var percRemaining = 1 - percTotal 291 | oneFilePerc = (1 - percTotal) / numWOPerc; 292 | 293 | for (var i = 0, len = this.urls.length; i < len; i++) { // eslint-disable-line no-redeclare 294 | if (this.percentageOfLoad[ this.urls[ i ] ]) { 295 | this.percentageOfLoad[ this.urls[ i ] ] *= percScale; 296 | } else { 297 | this.percentageOfLoad[ this.urls[ i ] ] = oneFilePerc; 298 | } 299 | } 300 | } 301 | }, 302 | 303 | /** 304 | * 305 | * With every call, assets are successively loaded and percentLoaded is updated 306 | * 307 | * @method _continueLoadQueue 308 | */ 309 | _continueLoadQueue: function () { 310 | if (this.loadIdx < this.urls.length) { 311 | var url = this.urls[ this.loadIdx ]; 312 | var loader = this.loaders[url]; 313 | this.status[url] = false; 314 | 315 | this.loadIdx++; 316 | loader.on('progress', this._onLoadProgress.bind(this, url)); 317 | loader.once('error', this._onLoadError.bind(this, url)); 318 | loader.once('complete', this._onLoadComplete.bind(this, url)); 319 | loader.load(url); 320 | } else if (this._checkComplete()) { 321 | this.emit('complete'); 322 | } 323 | }, 324 | 325 | /** 326 | * 327 | * Logs error, updates progress, and continues the load 328 | * 329 | * 330 | * @method _onLoadError 331 | * @param {String} url of current loading item 332 | * @param {String} error Error message/type 333 | */ 334 | _onLoadError: function (url, error) { 335 | console.warn('Couldn\'t load ' + url + ' received the error: ' + error); 336 | 337 | var curPerc = this.percentageOfLoad[ url ]; 338 | 339 | this.emit('progress', this.percTotal + curPerc, url); 340 | this.status[url] = true; 341 | this._continueLoadQueue(); 342 | }, 343 | 344 | /** 345 | * 346 | * Calculates progress of currently loading asset and dispatches total load progress 347 | * 348 | * 349 | * @method _onLoadProgress 350 | * @param {String} url of current loading item 351 | * @param {Number} progress Progress of currently loading asset 352 | */ 353 | _onLoadProgress: function (url, progress) { 354 | var curPerc = this.percentageOfLoad[ url ] * progress; 355 | 356 | this.emit('progress', this.percTotal + curPerc, url); 357 | }, 358 | 359 | /** 360 | * 361 | * Marks url as complete and updates total load percentage 362 | * 363 | * 364 | * @method _onLoadComplete 365 | * @param {String} url of current loading item 366 | * @param {Object} content The loaded content 367 | */ 368 | _onLoadComplete: function (url, content) { 369 | this.percTotal += this.percentageOfLoad[ url ]; 370 | this.status[url] = true; 371 | this._continueLoadQueue(); 372 | }, 373 | 374 | /** 375 | * 376 | * Returns true / false depending on if all url are finished loading or not 377 | * 378 | * 379 | * @method _checkComplete 380 | * @return {Boolean} Is loading done? 381 | */ 382 | _checkComplete: function () { 383 | var loaded = true; 384 | for (var i = 0, len = this.urls.length; i < len; i++) { 385 | if (!this.status[this.urls[ i ]]) loaded = false; 386 | } 387 | return loaded; 388 | }, 389 | 390 | /** 391 | * 392 | * Retrieves the appropriate loader util given the asset file-type 393 | * 394 | * 395 | * @method _getLoader 396 | * @param {String} url Base URL of asset 397 | * @return {Function} Chosen loader util function based on filetype/extension 398 | */ 399 | _getLoader: function (url) { 400 | var extension = getExtension(url); 401 | var loader = LOADER_DEFAULT; 402 | if (extension && LOADERS[ extension.toLowerCase() ]) loader = LOADERS[ extension.toLowerCase() ]; 403 | return loader; 404 | } 405 | }); 406 | 407 | module.exports = Preloader; 408 | --------------------------------------------------------------------------------