├── .gitignore ├── README.md ├── component.json ├── index.js ├── package.json └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | image-progress 2 | ========= 3 | 4 | A wrapper for loading image via XHR and dispatching progress events. 5 | 6 | ## How to use 7 | 8 | ### 1. Install the plugin. 9 | With npm: 10 | ``` 11 | npm i image-progress --save 12 | ``` 13 | 14 | With Component(1): 15 | ``` 16 | component install ayamflow/image-progress 17 | ``` 18 | 19 | ### 2. Use 20 | 21 | ```javascript 22 | var Progress = require('image-progress'); 23 | 24 | var img = new Progress('test-img.png'); 25 | 26 | img.on('error', function(event) { 27 | console.log('there has been an error', event); 28 | }); 29 | 30 | img.on('progress', function(event) { 31 | console.log('The image is ' + event.progress * 100 + '% loaded !', event.loaded, event.total, event.progress); 32 | }); 33 | 34 | img.on('complete', function(event) { 35 | console.log('The image is loaded.'); 36 | }); 37 | 38 | img.on('start', function(event) { 39 | console.log('The image with URL ' + event.url + ' has started loading'); 40 | }); 41 | 42 | img.load(); 43 | ``` 44 | 45 | By default, the `event.progress` only has 2 decimals. You can set the number of decimals by passing the `leading` property as an instanciation option. 46 | 47 | ## Methods 48 | * ### `new Progress(url, params)` 49 | 50 | `url`: the URL for the image you want to load. 51 | 52 | `params`: the params hash is used if you need to store & retrieve any property on the `start` & `complete` events. You can also pass different options there (see the options section below). 53 | 54 | * ### `load()` 55 | 56 | Starts the loading. It will fire a `start` event. 57 | 58 | * ### `destroy()` 59 | 60 | Removes all internal & external listeners, and clears the XHR object. 61 | 62 | By default, this method is called after the `complete` and/or `error` events are triggered. you can disable this behavior by passing the `autoclear: false` as an instanciation option. 63 | 64 | ## Instanciation options 65 | * ### `onStart`, `onError`, `onProgress`, `onComplete` (default: null) 66 | 67 | Callbacks to be called when the appropriate events are fired. 68 | 69 | * ### `autoload` (default: false) 70 | 71 | Wether the loading should start automatically on instanciation. If you set it to `true`, be sure to also pass `onProgress`/`onComplete` callbacks as well or you won't be able to listen for completion. 72 | 73 | * ### `leading` (default: 2) 74 | 75 | The number of decimals in the `event.progress` property. 76 | 77 | * ### `autoclear` (default: true) 78 | 79 | Set wether the `destroy` method should be automatically called after a `complete` or `error` event. 80 | 81 | * ### `jsonp` (default: false) 82 | 83 | Uses [jsonp](http://en.wikipedia.org/wiki/JSONP) in order to bypass [CORS](http://en.wikipedia.org/wiki/Cross-origin_resource_sharing) restrictions 84 | 85 | ## Events 86 | * `start`: fired when the loading starts. The event contains a reference to the `options` hash, as well as the `url`. 87 | * `progress`: fired each time the XHR request updates. The event has 3 properties: `loaded`, `total` and `progress`. 88 | * `complete'`: fired when the loading is complete. The event contains a reference to the `options` hash, as well as the `url`. 89 | * `error`: fired when a network-related error is raised. 90 | 91 | ## Properties 92 | * ### `total`: the total bytes to load 93 | * ### `loaded`: the loaded bytes loaded 94 | * ### `progress`: the loading progress, between 0 and 1 -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "image-progress", 3 | "repo": "ayamflow/image-progress", 4 | "description": "A wrapper for loading image via XHR and dispatching loading events.", 5 | "version": "1.0.3", 6 | "keywords": [ 7 | "image", 8 | "progress", 9 | "ajax", 10 | "xhr", 11 | "load" 12 | ], 13 | "license": "MIT", 14 | "main": "index.js", 15 | "scripts": [ 16 | "index.js" 17 | ] 18 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var EventEmitter = require('component-emitter'); 4 | var _ = require('lodash'); 5 | 6 | var ImageProgress = module.exports = function(url, params) { 7 | EventEmitter.call(this); 8 | 9 | if(!url) throw new Error('URL should be a valid string'); 10 | 11 | var callbackName = 'jsonp_callback_' + Math.round(100000 * Math.random()); 12 | 13 | this.options = _.merge({ 14 | autostart: false, 15 | autoclear: true, 16 | leading: 2, 17 | jsonp: false, 18 | }, params); 19 | 20 | this.options.url = url; 21 | 22 | if ( this.options.jsonp ) { 23 | this.options.url += (this.options.url.indexOf('?') >= 0 ? '&' : '?') + 'callback=' + callbackName; 24 | } 25 | 26 | this.loaded = 0; 27 | this.total = 0; 28 | this.progress = 0; 29 | 30 | this.request = new XMLHttpRequest(); 31 | this.request.onprogress = this.onProgress.bind(this); 32 | this.request.onload = this.onComplete.bind(this); 33 | this.request.onerror = this.onError.bind(this); 34 | 35 | if(this.options.autostart) this.load(); 36 | }; 37 | 38 | ImageProgress.prototype = Object.create(EventEmitter.prototype); 39 | ImageProgress.prototype.constructor = ImageProgress; 40 | 41 | ImageProgress.prototype.load = function() { 42 | this.request.open('GET', this.options.url, true); 43 | this.request.overrideMimeType('text/plain; charset=x-user-defined'); 44 | this.request.send(null); 45 | if(this.options.onStart) this.options.onStart(this.options); 46 | this.emit('start', this.options); 47 | }; 48 | 49 | ImageProgress.prototype.onProgress = function(event) { 50 | if(!event.lengthComputable) return; 51 | 52 | this.loaded = event.loaded; 53 | this.total = event.total; 54 | this.progress = +(this.loaded / this.total).toFixed(this.options.leading); 55 | 56 | var progressEvent = { 57 | loaded: this.loaded, 58 | total: this.total, 59 | progress: this.progress 60 | }; 61 | 62 | if(this.options.onProgress) this.options.onProgress(progressEvent); 63 | this.emit('progress', progressEvent); 64 | }; 65 | 66 | ImageProgress.prototype.onComplete = function(event) { 67 | if(this.options.onLoad) this.options.onLoad(this.options); 68 | this.emit('complete', this.options); 69 | if(this.options.autoclear) this.destroy(); 70 | }; 71 | 72 | ImageProgress.prototype.onError = function(event) { 73 | if(this.options.onError) this.options.onError(this.event); 74 | this.emit('error', event); 75 | if(this.options.autoclear) this.destroy(); 76 | }; 77 | 78 | ImageProgress.prototype.destroy = function(event) { 79 | if(this.request) { 80 | this.request.onprogress = null; 81 | this.request.onload = null; 82 | this.request.onerror = null; 83 | } 84 | this.request = null; 85 | this.off(); 86 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "image-progress", 3 | "version": "1.0.3", 4 | "description": "A wrapper for loading image via XHR and dispatching loading events.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node test/index.js | tap-spec" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/ayamflow/image-progress" 12 | }, 13 | "keywords": [ 14 | "image", 15 | "progress", 16 | "ajax", 17 | "xhr", 18 | "load" 19 | ], 20 | "author": "= <=>", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/ayamflow/image-progress/issues" 24 | }, 25 | "devDependencies": { 26 | "component-emitter": "^1.2.0", 27 | "tap-spec": "~0.2.0", 28 | "tape": "~2.13.3" 29 | }, 30 | "dependencies": { 31 | "component-emitter": "^1.1.3", 32 | "lodash": "^3.9.3" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'), 2 | Progress = require('../index.js'); 3 | 4 | function mockXMLHttpRequest(){} 5 | // Mock XMLHttpRequest 6 | XMLHttpRequest = mockXMLHttpRequest; 7 | XMLHttpRequest.prototype.open = XMLHttpRequest.prototype.overrideMimeType = XMLHttpRequest.prototype.send = mockXMLHttpRequest; 8 | 9 | test('Progress without argument', function (assert) { 10 | assert.plan(1); 11 | 12 | try { 13 | var progress = new Progress(); 14 | assert.fail('Progress didn\'t throw an error when called without URL parameter'); 15 | } 16 | catch(e) { 17 | assert.pass('Progress should throw an error when called without URL parameter'); 18 | } 19 | }); 20 | 21 | test('Progress initialization', function (assert) { 22 | assert.plan(3); 23 | 24 | var progress = new Progress('foo'); 25 | 26 | assert.equal(progress.loaded, 0, 'Loaded bytes should be null'); 27 | assert.equal(progress.total, 0, 'Total loaded bytes should be null'); 28 | assert.equal(progress.progress, 0, 'Loading progress should be null'); 29 | }); 30 | 31 | test('Progress with callbacks', function (assert) { 32 | assert.plan(1); 33 | 34 | var progress = new Progress('http://google.fr', { 35 | autostart: true, 36 | onStart: function() { 37 | assert.pass('onStart callback should be called.'); 38 | } 39 | }); 40 | }); --------------------------------------------------------------------------------