Search code examples
javascriptmootools

Load XML and the JS to process it in a strictly sequential order


I'm currently building a custom serialization function. This turns an array like this

{user_ids: [1,2,3], types: ['foo', 'bar']}

into a string like this:

ui-1,2,3,,ty-foo,bar

I have an XML file which tells me that "user_ids" maps to "ui", etc. I'm using XML2JSON to turn the XML file into something JavaScript can work with.

I want to store the abbreviations in local storage so the next time I need them I don't need to fetch the XML file or run the XML2JSON javascript again. However, I'm having trouble with getting everything to be executed in the correct order.

The abbreviation function can be called multiple times from various parts of the page. The first thing it does is that it checks whether the local storage is filled. If it's not, XML has to be loaded, XML2JSON has to be executed, the result has to be put in local storage, and only then I can continue.

Asset.javascript has an onLoad(), Request has onComplete, but they're not waiting nicely for eachother (seems that Asset.javascript is asynchronous no matter what).

I've tried to use Chain with callChain() in each function (so they only proceed once they have truly finished doing their part), but then I'm still stuck with having to return something - the result of the abbreviation function - which I can't figure out.

var Shorten = {

dict: [],

compact: function(args){

    if(dict.length == 0){

        // Check local storage
        if(!localStorage.dict){

            new Request({
                url: 'shorten.xml',
                contentType: 'application/xml',
                method: 'get',
                onSuccess: (function(rdf){

                    Asset.javascript('xml2json.js', {
                        id: 'xml2json',
                        onLoad: (function(){

                            var x2js = new X2JS();
                            var jsonObj = x2js.xml_str2json(rdf);
                            var dict = [];

                            jsonObj.parameters.alias.each(function(alias){

                                dict[alias._long] = alias._short;
                            });

                            localStorage.dict = dict;
                        })
                    });
                })
            }).send();
        }
        else{

            this.dict = localStorage.dict;
        }
    }
    else{

        args.each(function(item){

            // Check dictionary and replace with abbreviated version.
        });
    }
}
};

Solution

  • you cannot solve this in any meaningful way by returning immediately, you have 2 async methods to deal with.

    you could refactor along the lines of (done in 5 mins but you get the callback idea):

    var Shorten = {
    
        getSchema: function(rdf, done){
            Asset.javascript('xml2json.js', {
                id: 'xml2json',
                onLoad: function(){
                    var x2js = new X2JS(),
                        jsonObj = x2js.xml_str2json(rdf),
                        dict = {};
    
                    jsonObj.parameters.alias.each(function(alias){
                        dict[alias._long] = alias._short;
                    });
    
                    localStorage.setItem('dict', JSON.encode(dict));
                    done(dict);
                }
            });
        },
    
        compact: function(args, done){
            var self = this,
                dict = localStorage.getItem('dict'),
                process = function(dict){
                    args.each(function(item){
                    // Check dictionary and replace with abbreviated version.
                    });
                    done();
                };     
    
            if (!dict){
                // Check local storage
                new Request({
                    url: 'shorten.xml',
                    contentType: 'application/xml',
                    method: 'get',
                    onComplete: this.getSchema.bind(this, process)
                }).send();
            } else {
                process(JSON.decode(dict));
            }
        }
    };
    
    
    Shorten.compact([{user_ids: [1,2,3], types: ['foo', 'bar']}], function(result){
        console.log(result);
    });
    

    i've not had a chance to test this but it should get you started on the right path.

    once it's been resolved, schema is actually stored in localStorage so subsequent calls will return nice and fast - you could also refactor to make the schema prequisite to returning your module and make it sync like var shortString = Shorten.compact(args); - but you need to nest the schema calls beforehand - if you know it will need to happen, you may as well.

    you can also pre-load xml2json.js so you don't rely on Asset.javascript JIT lazy loading, meaning you can process things faster with just the one async call to get the schema. you could even make the ajax request synchronous (blocking UI) for the purpose of chaining and value immediate return, though callbacks / promises is a normal pattern for this kind of stuff

    p.s. dict is an Object hash, not an Array