Дело в том, что по работе мне пришлось учиться писать расширения для хрома. Так как задача сама по себе была небольшая, то первой мыслью естесственно было написать лапшекод в лоб. Но помучившись с получением и сохранением данных в localStorage, решил, что так дело не пойдет, и, уж как минимум, надо написать какую-нить обертку над localStorage.
Вообще в самом начале я честно погуглил на предмет наличия разных фреймворков для расширений. Нашел, например, Kango. Но мне не нужна была кроссбраузерность (расширение писалось только для Chrome), и не было желания заморачиваться изучением сторонних библиотек ради небольшого приложения, поэтому было решено написать свой велосипед.
Задачи, которые ставились перед контейнером:
- Удобная работа с localStorage. Получаем и сохраняем любые данные, в том числе объекты и массивы.
- Какая-нибудь валидация параметров (хотя бы в случае, если такого параметра в localStorage просто нет).
- Возможность фасовать параметры по группам — плохо когда все свалено в одну кучу.
- Подключение функциональности с разносом по отдельным компонентам, при этом вероятно будет необходима преинициализация некоторых параметров.
В принципе все удалось реализовать в небольшом контейнере, и для небольших приложений он меня устраивает на 100%.
Итак, у нас есть объект app, и он содержит всего 3 метода: addStorage, addValidator и addModule.
addStorage
Любое расширение должно где-то хранить настройки пользователя. ОК, напишем
app.addStorage('option');
После этого у нас появляется новый метод app.option(). Этот метод служит для сохранения и извлечения из localStorage параметров из группы option (смотрим хотелку #2: отделяем мух от котлет, эта группа только для пользовательских настроек, для других параметров заведем другой метод). app.option() принимает 2 аргумента: название параметра и его значение. 2-ой аргумент необязателен. Если он не указан, то метод вернет значение параметра, если указан — сохранит параметр с таким значением. app.option под капотом использует для сериализации JSON.stringify, а для извлечения данных — JSON.parse.
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] }
Естесственно, одними лишь настройками пользователя addStorage не ограничивается, там мы можем хранить любые данные, которые необходимо помещать в localStorage:
app.addStorage('whatever_you_want'); app.whatever_you_want('foo', 'bar');
addValidator
Этот метод добавляет правило валидации для конкретного параметра. Принимает 3 аргумента: название группы параметров, название параметра и собственно валидирующую функцию. Эта функция принимает как аргумент значение этого параметра и должна возвращать нужное значение. Каждый раз при вызове app.option(‘foo’) вызывается валидатор для foo, если он задан. Например:
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'
Это очень удобно в расширениях при первом запуске, так как никаких данных у нас еще нет.
addModule
addModule — это самое интересное. Он позволяет добавить в контейнер целый модуль. Принимает 2 аргумента: название и объект, содержащий методы или свойства.
app.addModule('foo', { bar: function() { // do something... } }); // Теперь можно вызвать метод bar app.foo.bar();
Естесственно, если бы все было так просто, смысла бы в этом не было. Добавляем немного магии. Во-первых, объект модуля может содержать метод init(). Тогда этот метод будет вызван при добавлении модуля (типа как конструктор). В нем прописываем любую инициализирующую логику для модуля. Во-вторых, модуль foo сам становится мини-копией контейнера app. Т.е. в нем самом становятся доступны функции addStorage, addValidator и даже addModule! Чтобы лучше продемонстрировать работу такого подхода, давайте попробуем реализовать модуль для локализации приложения.
// вся функциональность модуля состоит только из одной функции trans() - перевод фразы app.addModule('i18n', { // здесь будем хранить массив переводов data: {}, // настроим модуль init: function() { // где-то надо хранить локаль пользователя, заведем группу параметров option для модуля i18n, (!) эта не та же самая группа, которую мы использовали ранее this.addStorage('option'); // тут же валидатор, т.к. при первом запуске локаль не задана this.addValidator('option', 'locale', function(locale) { return 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]; } });
Немного подробнее о this.addStorage('option');
. Здесь мы создали группу параметров для настроек пользователя в модуле i18n. Она не совпадает с той, которой мы пользовались ранее, так создана в контексте объекта i18n. В приложении доступ к этой группе осуществляется через app.i18n.option('foo');
. Все это опять же сделано для хотелки #2: разделяем пользовательские настройки по модулям. Так лучше понятно что к чему относится.
Итак, что мы получили в итоге: четкую, ясную структуру кода, ничего не болтается в глобальной области видимости; возможность разбить логику приложения по разным компонентам; беспроблемную работу с localStorage — что положили, то и получили; валидация данных — один раз написали и забыли; ну и маленький исходный код контейнера — всего 30 строк 🙂
Disclaimer
Ни на какую 100% нужность и актуальность данного контейнера не претендую. Любая конструктивная логика приветствуется. Надеюсь, кто-то найдет интересную информацию в этом посте.
Полный текст кода контейнера
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(); } } };
ссылка на оригинал статьи http://habrahabr.ru/post/203024/
Добавить комментарий