В прошлой статье мы рассказали об основах Backbone Views и о том, как включить в jQuery тот же DOM, что и Backbone.
В этой статье мы рассмотрим, как заставить Backbone хорошо играть с d3.js. Концепции в этой статье должны применяться к ситуациям, когда вы собираетесь использовать Backbone с большинством других библиотек, которые также манипулируют DOM.
Backbone + d3.js работает на том же 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 + «!»;
});
|
Приведенный выше код выполняет следующие действия:
- Выбирает и создает ссылку на элемент
body
- В рамках этого выбора выбираются все элементы
p
которые в настоящее время находятся в DOM. - Добавляет каждый элемент в numericData к выбранному элементу
p
- Для каждого элемента
p
который еще не существует (то есть, некоторые элементы вnumericalData
которые еще нужно добавить), создает элементp
и добавляет его DOM - Устанавливает текстовый узел в каждом вновь созданном элементе
p
чтобы он содержал некоторый текст (включая соответствующий номер вnumericalData
)
Первая попытка сделать Backbone и d3.js играть красиво
Опираясь на то, что мы узнали в предыдущей статье, вот одна из реализаций общей манипуляции с 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()
.
Решения для получения d3.js и Backbone для приятной совместной игры
В этом простом примере существуют как минимум два простых решения.
- Используйте более конкретный селектор CSS
- Используйте другой тег в целом
Отключение части DOM
Для более сложных примеров нам могут понадобиться альтернативные стратегии. Одно из возможных решений — отключить часть 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
.
Инкапсулировать дифференцированную манипуляцию с DOM
Наконец, еще один подход к управлению функциями сторонних представлений заключается в использовании подпредставлений. Вот как это может выглядеть.
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
может содержать контент визуализации.
Еще один подход к использованию совместно используемой DOM для магистрали + d3.js
Есть моменты, когда вам нужно убедиться, что ваши 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.
- Обязательно выберите правильный элемент для манипуляции. Использование определенных классов CSS и идентификаторов может значительно облегчить жизнь.
- Умное использование различных методов манипулирования DOM в любой библиотеке может иметь большое значение. Обязательно уточните действие DOM (добавление, добавление, вставка, удаление), которое вам действительно необходимо.
- Циклические операции в DOM могут быть сложными. Убедитесь, что на правильные элементы воздействуют.
Существует также ряд альтернатив, которые необходимо рассмотреть, в зависимости от желаемого результата:
- Выделение части DOM
- Области действия DOM другой библиотеки в контексте Backbone
- Использование вложенных представлений для инкапсуляции воздействия других манипуляторов DOM