在同一页面中容纳多个 Backbone 视图、模型和集合

Accommodate multiple Backbone views, models and collections in the same page

我很难在同一页上显示两个 models/collections。

    <body>

<div id="mainContainer">
    <div id="contentContainer"></div>
</div>
<div id="mainContainer2">
    <div id="contentContainer2"></div>
</div>

<script id="list_container_tpl" type="text/template">
<div class="grid_5 listContainer">
    <div class="box">
        <h2 class="box_head grad_colour">Your tasks</h2>
        <div class="sorting">Show: <select id="taskSorting"><option value="0">All Current</option><option value="1">Completed</option></select>
            <input class="search round_all" id="searchTask" type="text" value="">
        </div>
        <div class="block">
            <ul id="taskList" class="list"></ul>
        </div>
    </div>
</div>
</script>

<script id="list2_container_tpl" type="text/template">
<div class="grid_5 mylistContainer">
    <div class="box">
        <h2 class="box_head grad_colour">Your facets</h2>
        <div class="sorting">
            %{--Show: <select id="taskSorting"><option value="0">All Current</option><option value="1">Completed</option></select>--}%
            <input class="search round_all" id="searchFacet" type="text" value="">
        </div>
        <div class="block">
            <ul id="facetList" class="list"></ul>
        </div>
    </div>
</div>
</script>


<script id="task_item_tpl" type="text/template">
<li class="task">
    <h4 class="name searchItem">{{ name }}</h4>
</li>
</script>
<script id="facet_item_tpl" type="text/template">
<li class="facet">
    <h5 class="label searchItem">{{ label }}</h5>
</li>
</script>

<script>
    var myapp = {
        model: {},
        view: {},
        collection: {},
        router: {}
    };
    var facetsSearch = {
        model: {},
        view: {},
        collection: {},
        router: {}
    };
</script>

<script src="underscore-min.js"></script>
<script src="handlebars.min.js"></script>
<script src="backbone-min.js"></script>
<script>
    /* avoid */
    _.templateSettings = {
        interpolate: /\{\{(.+?)\}\}/g
    };
</script>
<script>
    // model.tasks.js
    myapp.model.Tasks = Backbone.Model.extend({
        default:{
            completed: 0,
            name: ""
        },
        //url:"/js/libs/fixtures/task.json"
    });
    var tasks1 = new myapp.model.Tasks({
            completed: 0,
            name: "Clear dishes"
        }
    );
    var tasks2 = new myapp.model.Tasks({
            completed: 1,
            name: "Get out the trash"
        }
    );
    var tasks3 = new myapp.model.Tasks({
            completed: 0,
            name: "Do the laundry"
        }
    );
    var tasks4 = new myapp.model.Tasks({
            completed: 1,
            name: "Vacuuming the carpet"
        }
    );

    // collection.tasks.js
    myapp.collection.Tasks = Backbone.Collection.extend({
        currentStatus : function(status){
            return _(this.filter(function(data) {
                return data.get("completed") == status;
            }));
        },
        search : function(letters){
            if (letters == "") return this;

            var pattern = new RegExp(letters,"gi");
            return _(this.filter(function(data) {
                return pattern.test(data.get("name"));
            }));
        }
    });
    myapp.collection.tasks = new myapp.collection.Tasks([tasks1, tasks2, tasks3, tasks4]);

    // route.tasks.js
    myapp.router.Tasks = Backbone.Router.extend({
        routes: {
            "": "list",
        },
        list: function(){
            this.listContainerView = new myapp.view.TasksContainer({
                collection: myapp.collection.tasks
            });
            $("#contentContainer").append(this.listContainerView.render().el);
            this.listContainerView.sorts()
        }
    });
    myapp.router.tasks = new myapp.router.Tasks;

    <!-- render views -->
    myapp.view.TasksContainer = Backbone.View.extend({
        events: {
            "keyup #searchTask"     : "search",
            "change #taskSorting"   : "sorts"
        },
        render: function(data) {
            $(this.el).html(this.template);
            return this;
        },
        renderList : function(tasks){
            $("#taskList").html("");

            tasks.each(function(task){
                var view = new myapp.view.TasksItem({
                    model: task,
                    collection: this.collection
                });
                $("#taskList").append(view.render().el);
            });
            return this;
        },
        initialize : function(){
            this.template = _.template($("#list_container_tpl").html());
            this.collection.bind("reset", this.render, this);
        },
        search: function(e){
            var letters = $("#searchTask").val();
            this.renderList(this.collection.search(letters));
        },
        sorts: function(e){
            var status = $("#taskSorting").find("option:selected").val();
            if (status == "") status = 0;
            this.renderList(this.collection.currentStatus(status));
        }
    });
    myapp.view.TasksItem = Backbone.View.extend({
        events: {},
        render: function(data) {
            $(this.el).html(this.template(this.model.toJSON()));
            console.log(this.model.toJSON(), "became", this.template(this.model.toJSON()));
            return this;
        },
        initialize : function(){
            this.template = _.template($("#task_item_tpl").html());
        }
    });
</script>

<script>
    // model.facets.js
    facetsSearch.model.Facets = Backbone.Model.extend({
        default: {
            id: 0,
            label: "",
            facetValues: []
        }
    });

    var facet1 = new facetsSearch.model.Facets({
        id: 1,
        label: "Organism",
        facetValues: ["Orga1", "Orga2"]
    });
    var facet2 = new facetsSearch.model.Facets({
        id: 2,
        label: "Omics",
        facetValues: ["Omics1", "Omics2"]
    });
    var facet3 = new facetsSearch.model.Facets({
        id: 3,
        label: "Publication Date",
        facetValues: ["2016-11-01", "2016-11-02"]
    });

    // collection.facets.js
    facetsSearch.collection.Facets = Backbone.Collection.extend({
        search : function(letters){
            if (letters == "") return this;

            /**
             * the g modifier is used to perform a global match (find all matches rather than stopping after the first match).
             * Tip: To perform a global, case-insensitive search, use this modifier together with the "i" modifier.
             */
            var pattern = new RegExp(letters, "gi");
            return _(this.filter(function(data) {
                return pattern.test(data.get("label"));
            }));
        }
    });
    facetsSearch.collection.facets = new facetsSearch.collection.Facets([facet1, facet2, facet3]);

    // route.facets.js
    facetsSearch.router.Facets = Backbone.Router.extend({
        routes: {
            "": "list",
        },
        list: function(){
            this.mylistContainerView = new facetsSearch.view.FacetsContainer({
                collection: facetsSearch.collection.facets
            });
            console.log("Facet collection: ", facetsSearch.collection.facets);
            $("#contentContainer2").append(this.mylistContainerView.render().el);
            this.mylistContainerView.sorts()
        }
    });

    facetsSearch.router.Facets = new facetsSearch.router.Facets;

    facetsSearch.view.FacetsContainer = Backbone.View.extend({
        events: {
            "keyup #searchFacet" : "search",
            "change #facetSorting": "sorts"
        },
        render: function(data) {
            $(this.el).html(this.template);
            return this;
        },
        renderList : function(facets){
            $("#facetList").html("");

            facets.each(function(facet){
                var view2 = new facetsSearch.view.FacetsItem({
                    model: facet,
                    collection: this.collection
                });
                $("#facetList").append(view2.render().el);
            });
            return this;
        },
        initialize : function(){
            this.template = _.template($("#list2_container_tpl").html());
            this.collection.bind("reset", this.render, this);
        },
        search: function(e){
            var letters = $("#searchFacet").val();
            this.renderList(this.collection.search(letters));
        },
        sorts: function(e){
            /*var status = $("#taskSorting").find("option:selected").val();
             if (status == "") status = 0;
             this.renderList(this.collection.currentStatus(status));*/
        }
    });
    facetsSearch.view.FacetsItem = Backbone.View.extend({
        events: {},
        render: function(data) {
            $(this.el).html(this.template(this.model.toJSON()));
            console.log(this.model.toJSON(), "became", this.template(this.model.toJSON()));
            return this;
        },
        initialize : function(){
            this.template = _.template($("#facet_item_tpl").html());
        }
    });

</script>
<script>
    Backbone.history.start();
</script>
</body>

问题

您的方面上方显示任务。我创建了两串代码来呈现 TasksFacets 但分别修改了变量名称。不幸的是,前者无法显示。

您制作了 2 个路由器,它们的路由都是空的。每个路由都在 Backbone.history 中注册,因此当 facets 路由器初始化时,它的路由会覆盖任务路由器路由。

如何拥有多个路由器?

对于您的应用程序范围,您应该首先制作一个路由器,并在 parent 视图中处理包含 2 个列表的页面。为该页面制作一种 Layout 视图,它将处理 2 个列表:

var Layout = Backbone.View.extend({
    template: _.template($('#layout-template').html()),
    // keep the selector strings in a simple object
    selectors: {
        tasks: '.task-container',
        facets: '.facet-container',
    },
    initialize: function() {

        this.view = {
            tasks: new TaskList(),
            facets: new FacetList()
        };
    },
    render: function() {
        this.$el.html(this.template());
        var views = this.views,
            selectors = this.selectors;
        this.$(selectors.tasks).append(views.tasks.render().el);
        this.$(selectors.facets).append(views.facets.render().el);
        return this;
    }
});

那么,只有一台路由器:

var Router = Backbone.Router.extend({
    routes: {
        "": "list",
    },
    list: function() {
        this.listContainerView = new Layout();
        $("body").html(this.listContainerView.render().el);
    }
});

这不适用于您的代码 as-is,您必须自己将这些概念整合到您的应用程序中。

否则,如果你真的想要多个路由器,你必须明白它们不能共享一条路由,并且任何时刻只能触发一条路由。

当您有多个路由器时,每个路由器管理一个模块的路由。

var TaskRouter = Backbone.Router.extend({
    routes: {
        'tasks': 'taskList',
        'tasks/:id': 'taskDetails'
    }
    // ...snip...
});

var FacetsRouter = Backbone.Router.extend({
    routes: {
        'facets': 'facetList',
        'facets/:id': 'facetDetails'
    }
    // ...snip...
});

其他改进

编译模板一次

在扩展视图时编译一次模板比每次初始化新视图更有效。

myapp.view.TasksContainer = Backbone.View.extend({
    // gets compiled once
    template: _.template($("#list_container_tpl").html()),

    initialize: function() {
        // not here, as it gets compiled for each view
        // this.template = _.template($("#list_container_tpl").html())
    },
});

避免全局 jQuery 函数

有关其他信息,请参阅 What is the difference between $el and el

缓存 jQuery objects

每当您想 select 视图元素的 child 时,执行一次并将结果放入变量中。

render: function(data) {
    this.$el.html(this.template);
    // I like to namespace them inside an object.
    this.elements = {
        $list: this.$('.task-list'),
        $search: this.$('.task-sorting')
    };

    // then werever you want to use them
    this.elements.$list.toggleClass('active');

    return this;
},

使用listenTo

避免使用 bind/unbindon/off/once(别名)而使用 listenTo/stopListening/listenToOnce.

listenTo is an improved version of bind 解决内存泄漏问题。

this.collection.bind("reset", this.render, this);
// becomes
this.listenTo(this.collection, "reset", this.render);

将 objects 的数组传递给 collection

myapp.collection.Tasks = Backbone.Collection.extend({
    model: myapp.model.Tasks
    // ...snip...
});

myapp.collection.tasks = new myapp.collection.Tasks([{
    completed: 0,
    name: "Clear dishes"
}, {
    completed: 1,
    name: "Get out the trash"
}, {
    completed: 0,
    name: "Do the laundry"
}, {
    completed: 1,
    name: "Vacuuming the carpet"
}]);

这就够了,Backbone collection 处理剩下的事情。

正如 Emile 在他的详细 [​​=11=] 中提到的,您的问题是您正在使用相同的路由初始化多个路由器。

既然你似乎是从 Backbone 开始的,我会给你一个比创建复杂的布局(父)视图更简单的答案:

只需为特定路由设置一个处理程序,并在其中初始化您的两个视图。

它看起来像:

myapp.router = Backbone.Router.extend({
  routes: {
    "": "list",
  },
  list: function() {
    this.listContainerView = new myapp.view.TasksContainer({
      collection: myapp.collection.tasks
    });
    $("#contentContainer").append(this.listContainerView.render().el);

    this.listContainerView.sorts(); //this can be done inside the view
    this.mylistContainerView = new facetsSearch.view.FacetsContainer({
      collection: facetsSearch.collection.facets
    });
    $("#contentContainer2").append(this.mylistContainerView.render().el);
    this.mylistContainerView.sorts(); //this can be done inside the view
  }
});

您只需在同一路由中初始化 2 个视图。