Tiny 30-line gadget container on pure JS

Published on November 21, 2013

Tiny 30-line gadget container on pure JS

Having seen cool implementations of programs in 30 , 24 , 19 and even 1 line on the habrahabr, I also decided to fade the clean pages of the habr with something like that. Moreover, for a long time I wanted an invite, I had the opportunity to work in a new quality for myself.

The fact is that at work I had to learn to write extensions for chrome. Since the task itself was small, the first thought was naturally to write a noodle code in the forehead. But tormented by receiving and saving data in localStorage, I decided that this would not work, and, at least, I need to write some kind of thread wrapper over localStorage.

In general, at the very beginning I honestly googled for the presence of different frameworks for extensions. Found, for example,Kango . But I didn’t need cross-browser compatibility (the extension was written only for Chrome), and I didn’t want to bother studying third-party libraries for a small application, so I decided to write my own bike.

The tasks that were set before the container:
  • Convenient work with localStorage. We receive and save any data, including objects and arrays.
  • Some kind of validation of the parameters (if only if there is simply no such parameter in localStorage).
  • The ability to pack parameters into groups is bad when everything is piled up in one heap.
  • Connecting functionality with the separation of the individual components, while it will probably be necessary to initialize some parameters.

In principle, everything was implemented in a small container, and for small applications it suits me 100%.

So, we have an app object, and it contains only 3 methods: addStorage, addValidator and addModule.

addStorage

Any extension should store user settings somewhere. OK, write

app.addStorage('option');

After that, we have a new app.option () method. This method is used to save and retrieve parameters from localStorage parameters from the option group (see Wishlist # 2: we separate flies from cutlets, this group is only for user settings, we will use another method for other parameters). app.option () takes 2 arguments: the name of the parameter and its value. The 2nd argument is optional. If it is not specified, then the method will return the value of the parameter; if specified, it will save the parameter with this value. app.option under the hood uses JSON.stringify to serialize, and JSON.parse to retrieve data.

app.option('foo', 'bar'); // localStorage['option.foo'] = 'bar'
var a = app.option('foo'); // a = 'bar'
// можно сохранять массивы и объекты
app.option('foo', [1,2,3]);
var a = app.option('foo'); // a = [1,2,3]
app.option('foo', { bar: [1,2,3] });
var a = app.option('foo'); // a =  { bar: [1,2,3] }

Naturally, addStorage is not limited to user settings, we can store any data that needs to be placed in localStorage:

app.addStorage('whatever_you_want');
app.whatever_you_want('foo', 'bar');


addValidator

This method adds a validation rule for a specific parameter. It takes 3 arguments: the name of the parameter group, the name of the parameter, and the validating function itself. This function takes as argument the value of this parameter and should return the desired value. Each time you call app.option ('foo'), the validator for foo is called, if one is specified. For example:

var a = localStorage['option.foo']; // a = undefined
var a = app.option('foo'); // a = undefined
app.addValidator('option', 'foo', function(foo) {
    return typeof foo === 'undefined' ? 'bar' : foo;
});
var a = localStorage['option.foo']; // a = undefined
var a = app.option('foo'); // a = 'bar'

This is very convenient in extensions at the first start, since we do not have any data yet.

addModule

addModule is the most interesting. It allows you to add an entire module to the container. It takes 2 arguments: a name and an object containing methods or properties.

app.addModule('foo', {
    bar: function() {
        // do something...
    }
});
// Теперь можно вызвать метод bar
app.foo.bar();

Naturally, if everything was so simple, there would be no sense in this. Add some magic. First, a module object may contain an init () method. Then this method will be called when adding a module (such as a constructor). In it we prescribe any initializing logic for the module. Secondly, the foo module itself becomes a mini copy of the app container. Those. functions addStorage, addValidator and even addModule become available in it! To better demonstrate how this approach works, let's try to implement a module to localize the application.

// вся функциональность модуля состоит только из одной функции trans() - перевод фразы
app.addModule('i18n', {
    // здесь будем хранить массив переводов
    data: {},
    // настроим модуль
    init: function() {
        // где-то надо хранить локаль пользователя, заведем группу параметров option для модуля i18n, (!) эта не та же самая группа, которую мы использовали ранее
        this.addStorage('option');
        // тут же валидатор, т.к. при первом запуске локаль не задана
        this.addValidator('option', 'locale', function(locale) {
            return typeof locale === 'undefined' ? 'ru' : locale;
        });
        document.write('<script src="/path/to/i18n/' + this.option('locale') + '.js"><\/script>');
        this.data = data_from_file;
        $(document).ready(function() {
            $('.i18n').each(function() {
                $(this).text(this.trans($(this).attr('data-i18n')));
            });
        });
    },
    trans: function(field) {
        return typeof this.data[field] === 'undefined' ? field : this.data[field];
    }
});

A little more about this.addStorage('option');. Here we created a group of parameters for user settings in the i18n module. It does not coincide with the one we used earlier, it is so created in the context of the i18n object. In the application, access to this group is through app.i18n.option('foo');. All this is again done for Wishlist # 2: we divide user settings by modules. So it’s better to understand what's what.

So, what we got as a result: a clear, clear structure of the code, nothing hangs in the global scope; the ability to break the application logic into different components; trouble-free work with localStorage - what they put is what they got; data validation - once written and forgotten; Well, the container’s small source code is only 30 lines :)

Disclaimer

I do not pretend to any 100% need and relevance of this container. Any constructive logic is welcome. Hope someone finds some interesting information in this post.

Full text of container code

var app = {
    validator: {},
    addStorage: function(name) {
        this[name] = function(key, value) {
            var local_storage_name = typeof this.name === 'undefined' ? name : this.name + '.' + name;
            if (typeof value === 'undefined') {
                var param = typeof localStorage[local_storage_name + '.' + key] !== 'undefined' ? JSON.parse(localStorage[local_storage_name + '.' + key]) : null;
                if (typeof this.validator[name + '.' + key] !== 'undefined') {
                    param = this.validator[name + '.' + key](param);
                }
                return param;
            }
            localStorage[local_storage_name + '.' + key] = JSON.stringify(value);
        };
    },
    addValidator: function(storage, name, callback) {
        this.validator[storage + '.' + name] = callback;
    },
    addModule: function(name, object) {
        object.name = name;
        object.validator = {};
        object.addStorage = this.addStorage;
        object.addValidator = this.addValidator;
        object.addModule = this.addModule;
        this[name] = object;
        if (typeof this[name].init !== 'undefined') {
            this[name].init();
        }
    }
};