Статьи

Магистральные представления и DOM: часть 2

файл

В прошлой статье мы рассказали об основах Backbone Views и о том, как включить в jQuery тот же DOM, что и Backbone.

В этой статье мы рассмотрим, как заставить Backbone хорошо играть с d3.js. Концепции в этой статье должны применяться к ситуациям, когда вы собираетесь использовать Backbone с большинством других библиотек, которые также манипулируют DOM.

d3.js , написанный Майком Бостоком, является еще одной широко используемой библиотекой, которая манипулирует объектной моделью документа, прежде всего для визуализации данных. Фактически, вы можете проявить творческий подход к способу визуализации данных.

На самом низком уровне d3.js взаимодействует с элементами HTML и / или SVG и управляет их атрибутами, связывая данные с объектной моделью документа.

Вот краткий пример того, как работает d3:

1
2
3
4
5
6
7
8
9
var numericalData = [1,2,3,4,5,6];
 
d3.select(“body”).selectAll(“p”)
    .data(numericalData)
    .enter()
        .append(“p”)
        .text(function(d) {
            return “I’m number ” + d + “!”;
        });

Приведенный выше код выполняет следующие действия:

  1. Выбирает и создает ссылку на элемент body
  2. В рамках этого выбора выбираются все элементы p которые в настоящее время находятся в DOM.
  3. Добавляет каждый элемент в numericData к выбранному элементу p
  4. Для каждого элемента p который еще не существует (то есть, некоторые элементы в numericalData которые еще нужно добавить), создает элемент p и добавляет его DOM
  5. Устанавливает текстовый узел в каждом вновь созданном элементе p чтобы он содержал некоторый текст (включая соответствующий номер в numericalData )

Опираясь на то, что мы узнали в предыдущей статье, вот одна из реализаций общей манипуляции с DOM.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var CoolView = Backbone.View.extend({
 
    render: function() {
        var html = “<p>I am not a number!</p>”;
        this.$el.html(html);
        return this;
    },
 
    renderVisualization: function(arrayOfData) {
        d3.select(“body”)
            .selectAll(“p”)
            .data(arrayOfData)
            .enter()
                .append(“p”)
                .text(function(d) {
                    return “I’m number ” + d + “!”;
                });
    }
 
});
 
var coolView = new CoolView();
coolView.render();
 
var myData = [10, 9, 4, 2, 1];
coolView.renderWithD3(myData);

Предполагая, что нашей целью является сохранение существующего элемента p и добавление других элементов p в DOM, когда мы выполняем приведенный выше код, мы быстро сталкиваемся с серьезной проблемой.

.render() вставляет элемент p с текстом «Я не число» в DOM. Но .renderVisualization() выбирает все существующие p элементы в DOM и вставляет содержимое в эти элементы. Это переписывает текст в исходном элементе p , несмотря на то, что мы добавили его в DOM с помощью d3.append() .

В этом простом примере существуют как минимум два простых решения.

  1. Используйте более конкретный селектор CSS
  2. Используйте другой тег в целом

Для более сложных примеров нам могут понадобиться альтернативные стратегии. Одно из возможных решений – отключить часть DOM, которая будет использоваться одной из библиотек. Например:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
var CoolView = Backbone.View.extend({
    …
    renderVisualization: function(arrayOfData) {
        d3.select(“#visualization”)
            .selectAll(“p”)
            .data(arrayOfData)
            .enter()
                .append(“p”)
                .text(function(d) {
                    return “I’m number ” + d + “!”;
                });
    }
 
});
 
var coolView = new CoolView();
 
var myData = [10, 9, 4, 2, 1];
coolView.renderVisualization(myData);

В приведенном выше случае Backbone продолжает управлять созданным представлением. Тем не менее, предположим, что где-то в DOM есть элемент (внутри или снаружи DOM, управляемый представлением Backbone), идентификатором которого является «визуализация». Мы можем извлечь выгоду из этого факта, охватывая все наши DOM-связанные манипуляции с этим элементом. В результате все цепочечные методы d3, включая .selectAll("p") и .append("p") , выполняются в контексте #visualization .

Наконец, еще один подход к управлению функциями сторонних представлений заключается в использовании подпредставлений. Вот как это может выглядеть.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
var CoolView = Backbone.View.extend({
 
    render: function() {
 
        var subViewA = new SubViewA();
        var subViewB = new SubViewB();
 
        // set the html content for coolview
        this.$el.html(subViewA.render().el);
 
        // append more html content to coolview
        this.$el.append(subViewB.render().el);
        return this;
    }
 
});
 
// Elsewhere, perhaps in your router…
var coolView = new CoolView();
$(‘.app’).html(coolView.render().el);

В этом сценарии subViewA может содержать контент не-визуализации, а subViewB может содержать контент визуализации.

Есть моменты, когда вам нужно убедиться, что ваши d3 DOM манипуляции происходят в том же контексте, что и само представление Backbone. Например, простое отключение части DOM с помощью #visualization не гарантирует, что элемент существует внутри или снаружи части DOM, представленной представлением Backbone.

Один из вариантов – убедиться, что ваш начальный выбор d3 ссылается на тот же элемент, на который указывает this.$el . Тем не менее, если вы не хотите устанавливать el или какой-либо из его атрибутов напрямую, было бы сложно в достаточной степени ориентироваться на текущее представление с помощью CSS.

К счастью, в библиотеке d3.js существует метод, который позволяет прямой выбор узла. Позвольте мне представить вам d3.select(node) . Согласно документации, этот метод:

Выбирает указанный узел. Это полезно, если у вас уже есть ссылка на узел, такой как d3.select (this) в приемнике событий, или глобальный объект, такой как document.body. Эта функция не пересекает DOM.

Этот метод позволяет нам написать следующий код и гарантировать, что манипуляции d3 происходят в контексте представления Backbone.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var CoolView = Backbone.View.extend({
 
    // note that “el” is not set directly
 
    render: function() {
        var html = “<p>I am not a number!</p>”;
        this.$el.html(html);
        return this;
    },
 
    renderVisualization: function(arrayOfData) {
        d3.select(this);
 
        d3.select(this.el) //pass in a node reference
            .selectAll(“p”)
            .data(arrayOfData)
            .enter()
                .append(“p”)
                .text(function(d) {
                    return “I’m number ” + d + “!”;
                });
    }
});

Таким образом, есть несколько соображений, при которых Backbone хорошо сочетается с другими манипуляторами DOM.

  1. Обязательно выберите правильный элемент для манипуляции. Использование определенных классов CSS и идентификаторов может значительно облегчить жизнь.
  2. Умное использование различных методов манипулирования DOM в любой библиотеке может иметь большое значение. Обязательно уточните действие DOM (добавление, добавление, вставка, удаление), которое вам действительно необходимо.
  3. Циклические операции в DOM могут быть сложными. Убедитесь, что на правильные элементы воздействуют.

Существует также ряд альтернатив, которые необходимо рассмотреть, в зависимости от желаемого результата:

  1. Выделение части DOM
  2. Области действия DOM другой библиотеки в контексте Backbone
  3. Использование вложенных представлений для инкапсуляции воздействия других манипуляторов DOM