Himmators
Himmators

Reputation: 15006

I wrote some killer Javascript. How can I make it easily reusable?

I have been playing around with Chromes filestorage API. I have built a couple of functions that together automatically downloads a json-object and stores it as a string. If the last server-request was done within 24 hours. I automatically use the last version of the file. I use this for managing a huge data-dump that I do statistical analysis on.

The entire system only has one function that needs to be exposed. It's getData.

Currently all these functions are global variables. How should I make this contained in an orderly way.

//This file will cache serverdata every day.
var onInitFs,
errorHandler,
fileSystemInit,
saveFile,
readFile,
fileSystem,
getData;


//request rights to save files to system.
fileSystemInit = function(){
    //Browser specific
    window.requestFileSystem  = window.requestFileSystem || window.webkitRequestFileSystem;

    navigator.webkitPersistentStorage.requestQuota(1048*1048*256, function(grantedBytes) {
        //once approved (or if previously approved):
        window.requestFileSystem(PERSISTENT, grantedBytes, onInitFs, errorHandler);
    }, function(e) {
    console.log('Error', e);
    });
};

//make filesystem global.
onInitFs = function(fs) {
    fileSystem = fs;
};
fileSystemInit();

saveFile = function(url, content, callback){
    var filename = makeFilename(url)
    if(!fileSystem){
        console.log('no filesystem registered')
        return;
    }
    fileSystem.root.getFile(filename, {create: true}, function(fileEntry) {

        fileEntry.createWriter(function(fileWriter) {
            var blob = new Blob([JSON.stringify(content)], {type: 'application/json'});
            fileWriter.write(blob);

            fileWriter.onwriteend = function(e) {
                console.debug('Write completed.', e);
                if(callback){
                    callback();
                }
            };

            fileWriter.onerror = function(e) {
                console.log('Write failed: ', e);
            };
        }, errorHandler);

    }, errorHandler);
};

readFile = function(url, callback){
    var filename = makeFilename(url)
    if(!fileSystem){
        console.log('no filesystem registered');
        return;
    }

    fileSystem.root.getFile(filename, {}, function(fileEntry){

        //this object reads files.
        var reader = new FileReader();
        //register callback for read files
        reader.onloadend =  function(e){
            var callbackValue = JSON.parse(this.result)
            callback(callbackValue);
        };
        //read file-function
        fileEntry.file(function(file){
            reader.readAsText(file);
        },errorHandler);

    },errorHandler);
};

makeFilename = function(url){
    return  url.replace(/\W/g, '') +'.json'
}

errorHandler = function(e) {
  console.log('Error: ', e);
};

getData = function(url, callbackNewData, callbackOldData){
    var lastDownloaded = localStorage.getItem(url+'lastDownloaded'),
    oneDay = 1000*60*60*24;
    //update data if the data is old.
    window.setTimeout(function(){
        if(!lastDownloaded || new Date()-new Date(lastDownloaded) > oneDay ){
            console.debug('downloading '+url);
            d3.json(url, function(data){
                localStorage.setItem(url+'lastDownloaded',new Date());
                console.debug('saving '+url);
                saveFile(url, data, function(){
                    callbackNewData(url);
                });
            });
        }else{
            callbackOldData(url);
        }

    }, 200);
};

Upvotes: 2

Views: 127

Answers (4)

Tschallacka
Tschallacka

Reputation: 28722

Just prototype the heck out of it :-) And use some scoped instances(using var that = this) to pass elements back to the parent objects from different scopes.

Now you can just start a new FileSystemInstance() to do your magic.

If you wish to make the more "arcane" methods private you could consider moving them to a object within your object and such, but in the end anyone with true perserverance will be able to access them. So I advice to go with a public way, and name the private methods _fileSystemInit, so people who read the code know it's an internalised method.

//This file will cache serverdata every day.

function FileSystemInstance() {
    this.fileSystem = null;
    this.requestFileSystem = null;
    
    this.fileSystemInit();
    
    
}
//request rights to save files to system.
FileSystemInstance.prototype.fileSystemInit = function(){
    //Browser specific
    this.requestFileSystem  = window.requestFileSystem || window.webkitRequestFileSystem;
    this.requestFileSystem = this.requestFileSystem.bind(window);
    console.log(this.requestFileSystem);
	var that = this;
    console.log(that.requestFileSystem);
    navigator.webkitPersistentStorage.requestQuota(1048*1048*256, function(grantedBytes) {
        //once approved (or if previously approved):
        console.log(that.requestFileSystem);
        that.requestFileSystem(PERSISTENT, grantedBytes, function(fs){that.onInitFs(fs)}, function(e){that.errorHandler(e)});
    }, function(e) {
    console.log('Error', e);
    });
};

//make filesystem global.
FileSystemInstance.prototype.onInitFs = function(fs) {
    this.fileSystem = fs;
};


FileSystemInstance.prototype.saveFile = function(url, content, callback){
    var filename = this.makeFilename(url)
    if(!fileSystem){
        console.log('no filesystem registered')
        return;
    }
    this.fileSystem.root.getFile(filename, {create: true}, function(fileEntry) {

        fileEntry.createWriter(function(fileWriter) {
            var blob = new Blob([JSON.stringify(content)], {type: 'application/json'});
            fileWriter.write(blob);

            fileWriter.onwriteend = function(e) {
                console.debug('Write completed.', e);
                if(callback){
                    callback();
                }
            };

            fileWriter.onerror = function(e) {
                console.log('Write failed: ', e);
            };
        }, errorHandler);

    }, errorHandler);
};

FileSystemInstance.prototype.readFile = function(url, callback){
    var filename = this.makeFilename(url)
    if(!this.fileSystem){
        throw new Error('no filesystem registered');
    }

    this.fileSystem.root.getFile(filename, {}, function(fileEntry){

        //this object reads files.
        var reader = new FileReader();
        //register callback for read files
        reader.onloadend =  function(e){
            var callbackValue = JSON.parse(this.result)
            callback(callbackValue);
        };
        //read file-function
        fileEntry.file(function(file){
            reader.readAsText(file);
        },errorHandler);

    },errorHandler);
};

FileSystemInstance.prototype.makeFilename = function(url){
    return  url.replace(/\W/g, '') +'.json'
}

FileSystemInstance.prototype.errorHandler = function(e) {
  console.error('Error: ', e);
};

FileSystemInstance.prototype.getData = function(url, callbackNewData, callbackOldData){
    var that = this;
    var lastDownloaded = localStorage.getItem(url+'lastDownloaded'),
    oneDay = 1000*60*60*24;
    //update data if the data is old.
    window.setTimeout(function(){
        if(!lastDownloaded || new Date()-new Date(lastDownloaded) > oneDay ){
            console.debug('downloading '+url);
            d3.json(url, function(data){
                localStorage.setItem(url+'lastDownloaded',new Date());
                console.debug('saving '+url);
                that.saveFile(url, data, function(){
                    callbackNewData(url);
                });
            });
        }else{
            callbackOldData(url);
        }

    }, 200);
};
FileSystem = new FileSystemInstance();
var data = FileSystem.getData();
console.log("Data is: ",data);

Upvotes: 0

Anton Melnikov
Anton Melnikov

Reputation: 1058

The Module pattern would be a good start: http://addyosmani.com/resources/essentialjsdesignpatterns/book/#modulepatternjavascript

Pseudo-class also would be great:

var StorageInterface = function(arg1, arg2){
    this.arg1 = arg1;
    this.arg2 = arg2;
}

StorageInterface.prototype.method = function(arg3, arg4){
    return arg3 + arg4 + this.arg1 + this.arg2;
}

var si = new StorageInterface(100, 500);
si.method(3, 4);

Upvotes: 0

AdityaParab
AdityaParab

Reputation: 7100

(function(window){
'use strict'
var onInitFs,errorHandler,fileSystemInit,saveFile,readFile,fileSystem,getData;

fileSystemInit = function(){
    // your code
};
//make filesystem global.
onInitFs = function(fs) {
    //your code
};
fileSystemInit();

saveFile = function(url, content, callback){
   //your code
};

readFile = function(url, callback){
    //your code
};

makeFilename = function(url){
    //your code
}

errorHandler = function(e) {
  //your code
};

getData = function(url, callbackNewData, callbackOldData){
    //your code
};

window.HimmatorsFileStorageAPI = getData; //  you can change the name here

})(window);

And you can use it simply by including this script in your page and then calling

HimmatorsFileStorageAPI(url, callbackNewData, callbackOldData);

Upvotes: 0

FelisCatus
FelisCatus

Reputation: 5334

You can wrap the whole thing in an anonymous function and expose getData only. This is the easiest way to do.

var getDataFromUrl = function () {
  //This file will cache serverdata every day.
  var onInitFs,
  errorHandler,
  fileSystemInit,
  saveFile,
  readFile,
  fileSystem,
  getData;

  // Your original code here ...

  return getData; // This exposes the getData function.
})();

In this way you only exposes one global function getDataFromUrl, which is exactly the public API.

For more modern usage, you may want to check out Common JS Modules and Browserify, which let you do exports and require both in browser and NodeJS. There is also a UMD Pattern for exporting libraries.

Upvotes: 2

Related Questions