...

четверг, 12 сентября 2013 г.

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

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


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


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends:



Комментариев нет:

Отправить комментарий