for (var i = 0; i < times; i++)
result += string;
return result;
}
function TextCell(text) {
this.text = text.split("\n");
}
TextCell.prototype.minWidth = function() {
return this.text.reduce(function(width, line) {
return Math.max(width, line.length);
}, 0);
};
TextCell.prototype.minHeight = function() {
return this.text.length;
};
TextCell.prototype.draw = function(width, height) {
var result = [];
for (var i = 0; i < height; i++) {
var line = this.text[i] || "";
result.push(line + repeat(" ", width - line.length));
}
return result;
};
В коде используется вспомогательная функция repeat
, которая возвращает переданную первым аргументом строку, повторённую переданное вторым аргументом количество раз. Метод draw
использует её для создания отступов в ячейках, чтобы они все были необходимой длины.
Давайте нарисуем для опыта шахматную доску 5×5.
var rows = [];
for (var i = 0; i < 5; i++) {
var row = [];
for (var j = 0; j < 5; j++) {
if ((j + i) % 2 == 0)
row.push(new TextCell("##"));
else
row.push(new TextCell(" "));
}
rows.push(row);
}
console.log(drawTable(rows));
// → ## ## ##
// ## ##
// ## ## ##
// ## ##
// ## ## ##
Работает! Но так как у всех ячеек один размер, код форматирования таблицы не делает ничего интересного.
Исходные данные для таблицы гор, которую мы строим, содержатся в переменной MOUNTAINS
, их можно скачать тут.
Нам нужно выделить верхнюю строку, содержащую названия столбцов, при помощи подчёркивания. Никаких проблем – мы просто создаём тип ячейки, который этим занимается.
function UnderlinedCell(inner) {
this.inner = inner;
};
UnderlinedCell.prototype.minWidth = function() {
return this.inner.minWidth();
};
UnderlinedCell.prototype.minHeight = function() {
return this.inner.minHeight() + 1;
};
UnderlinedCell.prototype.draw = function(width, height) {
return this.inner.draw(width, height - 1)
.concat([repeat("-", width)]);
};
Подчёркнутая ячейка содержит другую ячейку. Она возвращает такие же размеры, как и у ячейки inner
(через вызовы её методов minWidth
и minHeight
), но добавляет единичку к высоте из-за места, занятого чёрточками.
Рисовать её просто – мы берём содержимое ячейки inner
и добавляем одну строку, заполненную чёрточками.
Теперь, имея основной движок, мы можем написать функцию, строящую сетку ячеек из нашего набора данных.
function dataTable(data) {
var keys = Object.keys(data[0]);
var headers = keys.map(function(name) {
return new UnderlinedCell(new TextCell(name));
});
var body = data.map(function(row) {
return keys.map(function(name) {
return new TextCell(String(row[name]));
});
});
return [headers].concat(body);
}
console.log(drawTable(dataTable(MOUNTAINS)));
// → name height country
// ------------ ------ -------------
// Kilimanjaro 5895 Tanzania
// … и так далее
Стандартная функция Object.keys
возвращает массив имён свойств объекта. Верхняя строка таблицы состоит из подчёркнутых ячеек с заголовками столбцов. Всё что ниже – значения из набора данных – имеет вид обычных ячеек. Мы извлекаем эти данные проходом функции map
по массиву keys
, чтобы гарантировать одинаковый порядок ячеек в каждой из строк.
Итоговая таблица напоминает таблицу из примера, только вот числа не выровнены по правому краю. Мы займёмся этим чуть позже.
Геттеры и сеттеры
При создании интерфейса можно ввести свойства, не являющиеся методами. Мы могли бы определить minHeight
и minWidth
как переменные для хранения чисел. Но это потребовало бы от нас написать код вычисления их значений в конструкторе, что плохо, поскольку эти операции не связаны напрямую с конструированием объекта. Это может аукнуться, когда, например, внутренняя ячейка подчёркнутой ячейки изменяется – в этот момент размер подчеркивания тоже должен измениться.
Эти соображения привели к тому, что свойства, не являющиеся методами, многие не включают в интерфейс. Вместо прямого доступа к свойствам-значениям, используются методы типа getSomething
и setSomething
для чтения и записи значений свойств. Но в таком подходе есть и минус – приходится писать (и читать) много дополнительных методов.