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

от автора

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

Подходит к вам заказчик и говорит: хочу табличку с этим вот массивом данных о команде юзеров (показывает массив из трех записей). А этот вот по имени Петя, он среди них главный — так ниже и напишите. Стоимость: 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: убьем в себе кодера.!

ссылка на оригинал статьи http://habrahabr.ru/post/193522/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *