在本文中,我们将探讨如何使 backbone 与 d3.js 完美配合。本文中的概念应该适用于您打算将 Backbone 与大多数其他也操作 dom 的库一起使用的情况。
Backbone + d3.js 在同一个 DOM 上工作
d3.js由 Mike Bostock 编写,是另一个广泛使用的库,它操纵文档对象模型,主要用于可视化数据。事实上,您可以通过数据可视化的方式获得真正的创意。
在最低级别,d3.js 与 html 和/或svg元素交互,并通过将数据绑定到文档对象模型来操作它们的属性。
以下是 d3 如何运行的快速示例:
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 中的所有元素
将每个元素附加numericalData到选定p元素
对于尚未存在的每个p元素(即其中的一些元素仍需要附加),创建一个元素并将其添加到 DOMnumericalDatap
将每个新创建的元素中的文本节点设置为包含一些文本(包括 中的相关数字) pnumericalData
第一次尝试让 Backbone 和 d3.js 玩得很好
利用我们在上一篇文章中学到的知识,这里是一个共享 DOM 操作的实现。
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() 插入到 DOM 中。p但是会.renderVisualization()选择 DOM 中的所有现有p元素并将内容插入到这些元素中。这会覆盖原始元素中的文本,p尽管我们使用.d3.append()
让 d3.js 和 Backbone 完美结合的解决方案
在这个简单的例子中,至少存在两个简单的解决方案。
使用更具体的css选择器
完全使用不同的标签
封锁 DOM 的一部分
对于更复杂的示例,我们可能需要替代策略。一种可能的解决方案是封锁将由其中一个库操作的 DOM 的一部分。例如:
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 中的某处(由 Backbone 视图管理的DOM 内部或外部)有一个 id 为“可视化”的元素。我们可以通过将所有与d3相关的 DOM 操作范围限定到该元素来了解这一事实。因此,所有 d3 链式方法,包括.selectAll("p")和.append("p"),都在#visualization.
用子视图封装差异化的 DOM 操作
最后,管理第三方视图功能的另一种方法是使用子视图。这是看起来的样子。
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可能包含可视化内容。
Backbone + d3.js 的另一种 Shared-DOM 操作方法
有时您需要确保您的 d3 DOM 操作发生在与 Backbone 视图本身相同的上下文中。例如,简单地封锁 DOM 的一部分并#visualization不能保证元素存在于由 Backbone 视图表示的 DOM 部分之内或之外。
一种替代方法是确保您的起始 d3 选择引用与 所指向的元素相同的元素this.$el。但是,如果您倾向于不直接设置el或其任何属性,则很难使用 css 充分定位当前视图。
幸运的是,在 d3.js 库中,有一种方法可以直接选择节点。让我给你介绍一下d3.select(node)。根据文档,此方法:
> 选择指定的节点。如果您已经拥有对节点的引用,例如事件***器中的 d3.select(this) 或全局(例如 document.body),这将非常有用。此函数不遍历 DOM。
此方法允许我们编写以下代码并确保 d3 操作发生在 Backbone 视图的上下文中。
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); // will not work, "this" refers to a Backbone object 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 类和 ID 可以让生活更轻松。
在任何一个库中巧妙地使用各种 DOM 操作方法都可以大有帮助。一定要明确你真正需要的 DOM 操作(追加、前置、插入、删除)。
DOM 中的循环操作可能很棘手。确保正确的元素受到影响。
- 第一次尝试让 Backbone 和 d3.js 玩得很好
- 休斯顿,我们有问题!
- 让 d3.js 和 Backbone 完美结合的解决方案
- 封锁 DOM 的一部分
- 用子视图封装差异化的 DOM 操作
- Backbone + d3.js 的另一种 Shared-DOM 操作方法