Архитектор или кодер?

в 14:22, , рубрики: Без рубрики

И так., сегодня поговорим о создании таблички с данными, и еще чего то. Строка таблички, к примеру, будет иметь два поля: имя и фамилию. Все просто. Куда уже проще. Возможно. Здесь есть несколько «подходов» реализации. Давайте их рассмотрим. С небольшой долей сарказма. В комментариях можно поднять тему важности построения правильной архитектуры, или опровергнуть.

Подходит к вам заказчик и говорит: хочу табличку с этим вот массивом данных о команде юзеров (показывает массив из трех записей). А этот вот по имени Петя, он среди них главный — так ниже и напишите. Стоимость: 30 y.e. Сроки: 1 час.
Вы: Ок. Нет проблем.


1. Ну, ту вы включаете все свои архитекторские скилы и начинаете: создаем структуру приложения, положили index.html, в папочку js закинули файлик app.js. Создали первую конструкцию:

(function() {
    var app = {

        config: { },

        init: function() { }
   };

  app.init();
})();

Гуд. Все работает. Круто.
Продолжим… Делаем паузу, и начинаем пытаться придумать как же нам нарисовать эту табличку с данными. Данные статические (так и хотел заказчик), поэтому нам не грозят всякие там «аяксы» и другие страшные вещи. Кстати вот и сами «некоторые данные».

       someData: [
            {name: 'Вася', surName: 'Шариков'},
            {name: 'Саша', surName: 'Пупкин'},
            {name: 'Петя', surName: 'Иванов'}
        ],

Ну раз статические, так и закинем их сразу в наше приложение. Ну, можем еще за раз создать и вызвать функцию создания таблички и подписи о тимлиде (да, вынесем сразу нашего Петю в конфиг).

Длинный код

(function() {
    var app = {

        config: {
            teamLeadName: 'Петя'
        },

       someData: [
            {name: 'Bob', surName: 'Smith'},
            {name: 'Jack', surName: 'Smith'},
            {name: 'Nick', surName: 'Smith'}
        ],

        init: function() { 
             var app = this;

             return app;
        },

        createTable: function() { 
             var app = this;

             return app; 
        },

        createTeamLeadInfoBlock: function() { }
   };

  app.init().createTable().createTeamLeadInfoBlock();
})();

На сколько сложной может быть вьюха строки юзера? Как часто она может изменятся? Как удобней было бы с ней работать? Да, вроде напрашивается использования шаблонизатора. Создадим наш шаблон для строки юзера в html файлике.

       <script id="userTpl" type="text/template">
            <tr>
                <td>{name}</td>
                <td>{surName}</td>
            </tr>
       </script>

В нашем случае это достаточно простой шаблон. Может кстати сейчас уже посмотреть на весь код файлика index.html, он уже не будет изменятся.

 <html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    </head>

    <body>
        <table id="usersTable"></table>

        <div id="temLeadInfoBlock"></div>

        <script id="userTpl" type="text/template">
            <tr>
                <td>{name}</td>
                <td>{surName}</td>
            </tr>
        </script>

        <script src="js/app.js"></script>
    </body>
</html>

Вот и все. Весь наш штмл. Гуд.
Ну, что же, пора заюзать шаблон и что то вставить. Посмотрим как изменился наш код

Длинный код

(function() {
    var app = {

        config: {
            teamLeadName: 'Петя'
        },

       someData: [
            {name: 'Bob', surName: 'Smith'},
            {name: 'Jack', surName: 'Smith'},
            {name: 'Nick', surName: 'Smith'}
        ],

        init: function() { 
             var app = this;

             app.utils = app.getUtils();

             return app;
        },

        createTable: function() { 
             var app = this;

             app.utils.insertData('userTpl', app.someData, 'usersTable');

             return app; 
        },

        createTeamLeadInfoBlock: function() { 
              var app = this,
                     utils = app.utils,
                     parentView,
                     data,
                     tpl = '<p><span>Team lead - </span><b>{name} {surName}<b></p>',
                     info;

             parentView = utils.getById('temLeadInfoBlock');

             data = utils.getById(app.config.teamLeadName, app.someData, 'name');

             info = utils.applyData(tpl, data);

             utils.setHtml(parentView, info);
        },
        
        getUtils: function() { }
   };

  app.init().createTable().createTeamLeadInfoBlock();
})();

Посмотрим что у нас тут появилось. Во первых

        getUtils: function() { }

Как вы уже догадались она и будет делать основную часть работы. При реальной работе, данный функционал надо выносить в отдельный файл, но здесь я этого не стал делать, так как это и есть по сути наш основной код, но тем не менее написал её (getUtils) максимально гибкой для вынесения в другой файл и с контекстом не зависящим от нашего апликейшена.
Смотрим далее. В создании таблички видим один из методов нашей утилиты

         app.utils.insertData('userTpl', app.someData, 'usersTable');

Хм, похоже на то, что это берет наш шаблон по идентификатору (userTpl), берет наши данные, и вставляет (наверное) в нашу таблицу (usersTable). Выглядит достаточно мило.
Идем далее, посмотрим теперь на создании блока с информацией о тимлиде

      createTeamLeadInfoBlock: function() { 
            var app = this,
                     utils = app.utils,
                     parentView,
                     data,
                     tpl = '<p><span>Team lead - </span><b>{name} {surName}<b></p>',
                     info;

             parentView = utils.getById('temLeadInfoBlock');

             data = utils.getById(app.config.teamLeadName, app.someData, 'name');

             info = utils.applyData(tpl, data);

             utils.setHtml(parentView, info);
     },

Первое что бросается в глаза — это что здесь мы уже сами вручную создали шаблон (tpl), а не получаем его из нашего штмл. Это тоже бывает полезно.
Гуд. Следующая полезная функция

             parentView = utils.getById('temLeadInfoBlock');

Не трудно догадаться, что оно вернуло нам ссылку на штмл-элемент по его идентификатору, но что мы видим далее —

            data = utils.getById(app.config.teamLeadName, app.someData, 'name');

Хм… а это похоже на то, что мы получили обьект с массива, найдя его по заданному полю (name) когда оно будет равно app.config.teamLeadName (что как вы помните есть именем тимлида — Петя). Теперь у нас есть обьект с данными о тимлиде.
Ну и последние две строчки

             info = utils.applyData(tpl, data);

             utils.setHtml(parentView, info);

Не трудно догадаться, что мы «насадили» данные на шаблон и записали их в DOM. Гуд. Осталось самое интересное, посмотреть что же дают нам наши утилиты
посмотрим что они из себя представляют:

Длинный код

    getUtils: function() {
            var app = this,
                config = app.config;

            return {

                insertData: function(tplId, data, viewId) {
                    var utils = this,
                        tplView = utils.getHtmlView(tplId),
                        tpl = tplView.innerHTML,
                        prevHtml = '',
                        resultHtml = '';

                    if(!utils.isArray(data)) {
                        resultHtml = utils.applyData(tpl, data);
                    } else {
                        utils.forEach(data, function(item, index) {
                            resultHtml += utils.applyData(tpl, item);
                        });
                    }

                    if (viewId) {
                        utils.setHtml(viewId, resultHtml);
                    }

                    return resultHtml;
                },

                applyData: function (tpl, obj) {
                    var tplSymbols = config.tplSymbols,
                        key;

                    for (key in obj) {
                        if (obj.hasOwnProperty(key)) {
                            tpl = tpl.replace(new RegExp(tplSymbols[0] + key + tplSymbols[1], 'ig'), obj[key]);
                        }
                    }

                    return tpl;
                },

                setHtml: function(view, resultHtml) {
                    var utils = this,
                        prevHtml = '';

                    view = utils.getHtmlView(view);

                    prevHtml = view.innerHTML;
                    view.innerHTML = prevHtml + resultHtml;
                },

                getHtmlView: function(view) {
                    var utils = this,
                        result;

                    if (typeof view === 'string') {
                        result = utils.getById(view);
                    } else {
                        result = view;
                    }

                    return result;
                },

                isArray: function(arr) {
                    return toString.call(arr) === '[object Array]';
                },

                forEach: function(arr, fn) {
                    var i, max;

                    for (i = 0, max = arr.length; i < max; i++) {
                        fn(arr[i], i);
                    }
                },

                getById: function(id, arr, idProperty) {
                    var utils = this,
                        i;

                    if (utils.isArray(arr)) {
                        for (i = arr.length; i--;) {
                            if (arr[i] && idProperty && arr[i][idProperty] == id) {
                                return arr[i];
                            }
                        }
                    } else {
                        return document.getElementById(id);
                    }

                    return null;
                }
            }
        }

И так. Пройдемся по функциям, с конца.

  • getById: function(id, arr, idProperty) { — если передали только первый параметр, воспринимает его как идентификатор к штмл-элементу, ищет его в DOM. Часто бывает полезно получить какой то обьект с массива найдя его по какому то полю, что мы и сделали, передавая вторым параметром массив по которому искать и поле с которым сравнивать.
  • forEach: function(arr, fn) { — не более чем «синтаксический сахар», который реализует перебор массива. В функцию обработки элемента массива, кроме самого элемента передаем еще значения итератора, это тоже часто бывает полезно знать
  • isArray: function(arr) { — проверят является ли массивом входящий аргумент.
  • getHtmlView: function(view) { — позволяет нам работать с штмл-элементами как напрямую, так и через идентификатор. Если входящий аргумент строка, интерпретируем его как идентификатор и ищем соответствующий штмл-элемент, возвращаем его.
  • setHtml: function(view, resultHtml) { — дописываем штмл в переданный первым параметром родительский штмл-элемент.
  • applyData: function (tpl, obj) { — «накладывает» обьект с данными на шаблону. Можно увидеть что, я вынес символы сигнализирующие о замене значений в шаблоне в конфиг аппликейшена (tplSymbols = config.tplSymbols), что делает наш шаблонизатор еще более гибким.
  • insertData: function(tplId, data, viewId) { — вставляет сразу наш шаблон с данными в родительский элемент (viewId). Он, кстати, может не быть передан, функция возвращает нам сгенерированный штмл, который мы сможем вставить попозже. Также data может быть либо объектом, либо массивом объектов.

Вот и все. Посмотрим. Теперь на код всего аппликейшена.

Длинный код

 (function() {
     var app = {

        config: {
            teamLeadName: 'Петя',
            tplSymbols: ['{','}']
        },

        someData: [
            {name: 'Вася', surName: 'Шариков'},
            {name: 'Саша', surName: 'Пупкин'},
            {name: 'Петя', surName: 'Иванов'}
        ],

        init: function() {
            var app = this;

            app.utils = app.getUtils();

            return app;
        },

        createTable: function() {
            var app = this;

            app.utils.insertData('userTpl', app.someData, 'usersTable');

            return app;
        },

        createTeamLeadInfoBlock: function() {
            var app = this,
                utils = app.utils,
                parentView,
                data,
                tpl = '<p><span>Team lead - </span><b>{name} {surName}<b></p>',
                info;

            parentView = utils.getById('temLeadInfoBlock');

            data = utils.getById(app.config.teamLeadName, app.someData, 'name');

            info = utils.applyData(tpl, data);

            utils.setHtml(parentView, info);
        },

        getUtils: function() {
            var app = this,
                config = app.config;

            return {

                insertData: function(tplId, data, viewId) {
                    var utils = this,
                        tplView = utils.getHtmlView(tplId),
                        tpl = tplView.innerHTML,
                        prevHtml = '',
                        resultHtml = '';

                    if(!utils.isArray(data)) {
                        resultHtml = utils.applyData(tpl, data);
                    } else {
                        utils.forEach(data, function(item, index) {
                            resultHtml += utils.applyData(tpl, item);
                        });
                    }

                    if (viewId) {
                        utils.setHtml(viewId, resultHtml);
                    }

                    return resultHtml;
                },

                applyData: function (tpl, obj) {
                    var tplSymbols = config.tplSymbols,
                        key;

                    for (key in obj) {
                        if (obj.hasOwnProperty(key)) {
                            tpl = tpl.replace(new RegExp(tplSymbols[0] + key + tplSymbols[1], 'ig'), obj[key]);
                        }
                    }

                    return tpl;
                },

                setHtml: function(view, resultHtml) {
                    var utils = this,
                        prevHtml = '';

                    view = utils.getHtmlView(view);

                    prevHtml = view.innerHTML;
                    view.innerHTML = prevHtml + resultHtml;
                },

                getHtmlView: function(view) {
                    var utils = this,
                        result;

                    if (typeof view === 'string') {
                        result = utils.getById(view);
                    } else {
                        result = view;
                    }

                    return result;
                },

                isArray: function(arr) {
                    return toString.call(arr) === '[object Array]';
                },

                forEach: function(arr, fn) {
                    var i, max;

                    for (i = 0, max = arr.length; i < max; i++) {
                        fn(arr[i], i);
                    }
                },

                getById: function(id, arr, idProperty) {
                    var utils = this,
                        i;

                    if (utils.isArray(arr)) {
                        for (i = arr.length; i--;) {
                            if (arr[i] && idProperty && arr[i][idProperty] == id) {
                                return arr[i];
                            }
                        }
                    } else {
                        return document.getElementById(id);
                    }

                    return null;
                }
            }
        }

    };

    app.init().createTable().createTeamLeadInfoBlock();

})();

Вот так мы в 150 строк сделали построения таблички. Тут конечно наша ленивая сторона, скажет
image
и предложит вам вот такое решение:

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    </head>

    <body>
        <table id="usersTable"></table>

        <div id="temLeadInfoBlock"></div>


        <script>
            (function() {
                var someData = [
                        {name: 'Вася', surName: 'Шариков'},
                        {name: 'Саша', surName: 'Пупкин'},
                        {name: 'Петя', surName: 'Иванов'}
                ], i, max, usersInfo = '';

                for (i = 0, max = someData.length; i<max; i++) {
                    usersInfo += '<tr><td>'+someData[i].name+'</td><td>'+someData[i].surName+'</td></tr>';
                }

                document.getElementById('usersTable').innerHTML = usersInfo;
                document.getElementById('temLeadInfoBlock').innerHTML = 
                '<p><span>Team lead - </span><b>'+someData[2].name +' '+someData[2].surName+'<b></p>';
            })();
        </script>

    </body>
</html>

Хм… намного короче и проще, не так ли?

И того: что мы получили от «ленивой реализации» — сэкономили кучу времени. Или нет? Конечно, смотря какие задачи мы решаем. Но то что это не возможно в дальнейшем поддерживать и дописывать — это факт. Только если переписать все заново. Так сэкономило ли это нам время?

PS: этой статьей я хочу начать цикл статей по JavaScript, делая каждый раз средней сложности приложения, делая ударения на качество кода и архитектуру. Надеюсь в комментариях меня многие будут исправлять, и мы будем видеть много точек зрения и решений конкретных задач. Если же все очень плохо (в этой статье) — продолжать не буду.
PPS: убьем в себе кодера.!

Автор: bob_lyashenko

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js