И так., сегодня поговорим о создании таблички с данными, и еще чего то. Строка таблички, к примеру, будет иметь два поля: имя и фамилию. Все просто. Куда уже проще. Возможно. Здесь есть несколько «подходов» реализации. Давайте их рассмотрим. С небольшой долей сарказма. В комментариях можно поднять тему важности построения правильной архитектуры, или опровергнуть.
Подходит к вам заказчик и говорит: хочу табличку с этим вот массивом данных о команде юзеров (показывает массив из трех записей). А этот вот по имени Петя, он среди них главный — так ниже и напишите. Стоимость: 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 строк сделали построения таблички. Тут конечно наша ленивая сторона, скажет
и предложит вам вот такое решение:
<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