Выбрать главу

  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 для чтения и записи значений свойств. Но в таком подходе есть и минус – приходится писать (и читать) много дополнительных методов.