123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- /**
- * @method DataMan
- * @public
- * @constructor
- * @param {File|Blob|ArrayBuffer|Uint8Array|String} data The data that you want to manipulate.
- * @param {String} [type] The data content (MIME) type, if known. Required if the first argument is an ArrayBuffer, Uint8Array, or URL
- */
- DataMan = function DataMan(data, type) {
- var self = this;
- if (!data) {
- throw new Error("DataMan constructor requires a data argument");
- }
- // The end result of all this is that we will have one of the following set:
- // - self.blob
- // - self.url
- // Unless we already have in-memory data, we don't load anything into memory
- // and instead rely on obtaining a read stream when the time comes.
- if (typeof File !== "undefined" && data instanceof File) {
- self.blob = data; // File inherits from Blob so this is OK
- self._type = data.type;
- } else if (typeof Blob !== "undefined" && data instanceof Blob) {
- self.blob = data;
- self._type = data.type;
- } else if (typeof ArrayBuffer !== "undefined" && data instanceof ArrayBuffer || EJSON.isBinary(data)) {
- if (typeof Blob === "undefined") {
- throw new Error("Browser must support Blobs to handle an ArrayBuffer or Uint8Array");
- }
- if (!type) {
- throw new Error("DataMan constructor requires a type argument when passed an ArrayBuffer or Uint8Array");
- }
- self.blob = new Blob([data], {type: type});
- self._type = type;
- } else if (typeof data === "string") {
- if (data.slice(0, 5) === "data:") {
- self._type = data.slice(5, data.indexOf(';'));
- self.blob = dataURItoBlob(data, self._type);
- } else if (data.slice(0, 5) === "http:" || data.slice(0, 6) === "https:") {
- if (!type) {
- throw new Error("DataMan constructor requires a type argument when passed a URL");
- }
- self.url = data;
- self._type = type;
- } else {
- throw new Error("DataMan constructor received unrecognized data string");
- }
- } else {
- throw new Error("DataMan constructor received data that it doesn't support");
- }
- };
- /**
- * @method DataMan.prototype.getBlob
- * @public
- * @param {Function} [callback] - callback(error, blob)
- * @returns {undefined|Blob}
- *
- * Passes a Blob representing this data to a callback or returns
- * the Blob if no callback is provided. A callback is required
- * if getting a Blob for a URL.
- */
- DataMan.prototype.getBlob = function dataManGetBlob(callback) {
- var self = this;
- if (callback) {
- if (self.blob) {
- callback(null, self.blob);
- } else if (self.url) {
- var xhr = new XMLHttpRequest();
- xhr.open('GET', self.url, true);
- xhr.responseType = "blob";
- xhr.onload = function(data) {
- self.blob = xhr.response;
- callback(null, self.blob);
- };
- xhr.onerror = function(err) {
- callback(err);
- };
- xhr.send();
- }
- } else {
- if (self.url)
- throw new Error('DataMan.getBlob requires a callback when managing a URL');
- return self.blob;
- }
- };
- /**
- * @method DataMan.prototype.getBinary
- * @public
- * @param {Number} [start] - First byte position to read.
- * @param {Number} [end] - Last byte position to read.
- * @param {Function} callback - callback(error, binaryData)
- * @returns {undefined}
- *
- * Passes a Uint8Array representing this data to a callback.
- */
- DataMan.prototype.getBinary = function dataManGetBinary(start, end, callback) {
- var self = this;
- if (typeof start === "function") {
- callback = start;
- }
- callback = callback || defaultCallback;
- function read(blob) {
- if (typeof FileReader === "undefined") {
- callback(new Error("Browser does not support FileReader"));
- return;
- }
- var reader = new FileReader();
- reader.onload = function(evt) {
- callback(null, new Uint8Array(evt.target.result));
- };
- reader.onerror = function(err) {
- callback(err);
- };
- reader.readAsArrayBuffer(blob);
- }
- self.getBlob(function (error, blob) {
- if (error) {
- callback(error);
- } else {
- if (typeof start === "number" && typeof end === "number") {
- var size = blob.size;
- // Return the requested chunk of binary data
- if (start >= size) {
- callback(new Error("DataMan.getBinary: start position beyond end of data (" + size + ")"));
- return;
- }
- end = Math.min(size, end);
- var slice = blob.slice || blob.webkitSlice || blob.mozSlice;
- if (typeof slice === 'undefined') {
- callback(new Error('Browser does not support File.slice'));
- return;
- }
- read(slice.call(blob, start, end, self._type));
- } else {
- // Return the entire binary data
- read(blob);
- }
- }
- });
- };
- /** @method DataMan.prototype.saveAs
- * @public
- * @param {String} [filename]
- * @return {undefined}
- *
- * Tells the browser to save the data like a normal downloaded file,
- * using the provided filename.
- *
- */
- DataMan.prototype.saveAs = function dataManSaveAs(filename) {
- var self = this;
- if (typeof window === "undefined")
- throw new Error("window must be defined to use saveLocal");
- if (!window.saveAs) {
- console.warn('DataMan.saveAs: window.saveAs not supported by this browser - add cfs-filesaver package');
- return;
- }
- self.getBlob(function (error, blob) {
- if (error) {
- throw error;
- } else {
- window.saveAs(blob, filename);
- }
- });
- };
- /**
- * @method DataMan.prototype.getDataUri
- * @public
- * @param {function} callback callback(err, dataUri)
- */
- DataMan.prototype.getDataUri = function dataManGetDataUri(callback) {
- // XXX: We could consider using: URL.createObjectURL(blob);
- // This will create a reference to the blob data instead of a clone
- // This is part of the File API - as the rest - Not sure how to generally
- // support from IE10, FF26, Chrome 31, safari 7, opera 19, ios 6, android 4
- var self = this;
- if (typeof callback !== 'function')
- throw new Error("getDataUri requires callback function");
- if (typeof FileReader === "undefined") {
- callback(new Error("Browser does not support FileReader"));
- return;
- }
- var fileReader = new FileReader();
- fileReader.onload = function(event) {
- var dataUri = event.target.result;
- callback(null, dataUri);
- };
- fileReader.onerror = function(err) {
- callback(err);
- };
- self.getBlob(function (error, blob) {
- if (error) {
- callback(error);
- } else {
- fileReader.readAsDataURL(blob);
- }
- });
- };
- /**
- * @method DataMan.prototype.size
- * @public
- * @param {function} [callback] callback(err, size)
- *
- * Passes the size of the data to the callback, if provided,
- * or returns it. A callback is required to get the size of a URL on the client.
- */
- DataMan.prototype.size = function dataManSize(callback) {
- var self = this;
- if (callback) {
- if (typeof self._size === "number") {
- callback(null, self._size);
- } else {
- self.getBlob(function (error, blob) {
- if (error) {
- callback(error);
- } else {
- self._size = blob.size;
- callback(null, self._size);
- }
- });
- }
- } else {
- if (self.url) {
- throw new Error("On the client, DataMan.size requires a callback when getting size for a URL on the client");
- } else if (typeof self._size === "number") {
- return self._size;
- } else {
- var blob = self.getBlob();
- self._size = blob.size;
- return self._size;
- }
- }
- };
- /**
- * @method DataMan.prototype.type
- * @public
- *
- * Returns the type of the data.
- */
- DataMan.prototype.type = function dataManType() {
- return this._type;
- };
- /**
- * @method dataURItoBlob
- * @private
- * @param {String} dataURI The data URI
- * @param {String} dataTYPE The content type
- * @returns {Blob} A new Blob instance
- *
- * Converts a data URI to a Blob.
- */
- function dataURItoBlob(dataURI, dataTYPE) {
- var str = atob(dataURI.split(',')[1]), array = [];
- for(var i = 0; i < str.length; i++) array.push(str.charCodeAt(i));
- return new Blob([new Uint8Array(array)], {type: dataTYPE});
- }
- /**
- * @method defaultCallback
- * @private
- * @param {Error} [err]
- * @returns {undefined}
- *
- * Can be used as a default callback for client methods that need a callback.
- * Simply throws the provided error if there is one.
- */
- function defaultCallback(err) {
- if (err) {
- // Show gentle error if Meteor error
- if (err instanceof Meteor.Error) {
- console.error(err.message);
- } else {
- // Normal error, just throw error
- throw err;
- }
- }
- }
|