在路由器中管理视图实例化是一种好习惯吗?

时间:2014-12-17 04:11:48

标签: backbone.js

所以这是我的第一个Backbone项目,我想知道我是否以最好的方式做事。我的应用程序基本上有两种状态,一种显示搜索框,另一种显示一个搜索框,下面有一个表格。我的路由器有搜索路径和只有搜索视图的初始登录页面。当用户键入查询时,路由器将导航到搜索路径,并将表视图添加到页面中。这是我的路由器:

app.Router = Backbone.Router.extend({

    routes: {
        '': 'index',
        'search/coords=:address&age=:age&rad=:rad': 'search'
    },

    search: function(address, age, rad){    
        app.statusView || (app.statusView = new app.StatusView());
        app.searchView || (app.searchView = new app.SearchView());
        app.trigger('status:loading');
        app.Practices.fetch({
            reset: false,
            success: function() {
                app.searchView.setElement($('#search-box')).render();
                var searchQuery = new app.SearchQueryModel({age: age, coords: address.split(","), radius: rad});
                if (!app.tableView){
                    app.tableView = new app.TableView({model: searchQuery});
                } else {
                    app.tableView.model = searchQuery;
                    app.tableView.refresh();
                };
            }
        });
        app.trigger('status:clear');
    },

    index: function() {
        app.statusView = new app.StatusView();
        app.searchView = new app.SearchView();
        app.footerView = new app.FooterView();
        app.searchView.setElement($('#search-box')).render();
    }
});

正如您所看到的,我的视图在索引路由中被实例化,然后在您搜索时使用相同的视图,除非用户直接进入搜索页面,在这种情况下视图将在那里实例化。如果这不是非常不理想的话,我会感到惊讶,因为检查视图是否已存在于搜索路径中似乎很笨拙。有没有更好的做事方式?

3 个答案:

答案 0 :(得分:0)

首先欢迎Backbone。这是一个可爱的框架,可以让你做出像你喜欢的美丽或丑陋的东西。根据良好实践,您的问题是关于视图实例化的位置。当然,通过处理url路由和视图实例来违反 Demeter法似乎有点不对。

但是观点必须从某个地方开始?如果不是路由器那么?

所以我有两个回复:

  1. 如果您的应用很简单而且您只想玩骨干,那么您可能会很好。很多人让单页应用程序框架使其他简单的应用程序复杂化。我不是想偷懒,但现在你拥有它是Backbone中自然的初学者选择。如果是这种情况,请停在这里。

  2. 如果您想使用骨干的全部功能来定制框架,请继续阅读。

  3. 所以我的设置旨在使用一些样板函数启动一个新项目,并只创建一些特定于新应用程序的类。路径处理和所有这类事情对我来说似乎很低级,它应该只是我不想经常看的某些配置的一部分。结果是我的路由器看起来像这样:

    define([
        'autorouter'
    ], function(AutoRouter){
        var AppRouter = AutoRouter.extend({
            autoRoutes: {
                ":page" : "routeDirect",
                ":page/:object" : "routeDirect",
                ":page/:object/:action" : "routeDirect",
                "": "routeDirect"
            }
        });
    
        return AppRouter;
    });
    

    然后,对于每个新项目,我都有一个文件,我保留非默认路由,例如:

    define(function(require){
       return {
            "schedule" : require('screens/schedule')
          , "logout" : require('screens/logout')
          , "login" : require('screens/login')
          , "create" : require('screens/create')
          , "upload" : require('screens/upload')
          , "select" : require('screens/selection')
          , "inventory" : require('screens/inventory')
          , "describe" : require('screens/description')
       }
    });
    

    我将每个屏幕放入其自己的文件中(使用requirejs进行多文件依赖关系管理)。额外的变量会传递给screen

    每个screen都是特定用户体验的大脑,负责加载视图,并在屏幕处于活动状态时处理某些事件。

    如果这看起来像一个有趣的设置,那么我就是这样做的:

    对于路由器本身,我使用了一个样板类,我从Derick Bailey借来了一些稍作修改:

    define([
        'jquery', 'underscore', 'backbone'],
    function($, _, Backbone) {
        var AutoRouter = Backbone.Router.extend({
          constructor: function(options){
              Backbone.Router.prototype.constructor.call(this, options);
              var that = this;
              that.app = options.app;
              if (this.autoRoutes){
                that.processAutoRoutes(options.app, that.autoRoutes);
              }
          },
    
          processAutoRoutes: function(app, autoRoutes){
              var method, methodName;
              var route, routesLength;
              var routes = [];
              var router = this;
    
              for(route in autoRoutes){
                routes.unshift([route, autoRoutes[route]]);
              }
    
              routesLength = routes.length;
              for (var i = 0; i < routesLength; i++){
                route = routes[i][0];
                methodName = routes[i][1];
                method = app[methodName];
                router.route(route, methodName, method);
              }
          }
        });
    
        return AutoRouter;
    });
    

    我永远不必看它,但我确实需要将它传递给app实例。例如:

    this.appRouter = new AppRouter({app : this});
    

    最后我的路线指示功能:

    define(function(require){
    
       var pathParser = function(path){
         return Array.prototype.slice.call(path);
       }
    
       var pathApply = function(path, routes, context){
         var pathArray = pathParser(path);
         var primary = pathArray[0];
         if (routes.hasOwnProperty(primary)){
             routes[primary].apply(context, pathArray.slice(1));
         } else {
             routes["default"].apply(context, pathArray.slice(1));
         }
       }
    
       return function(path){
         //NOTE PLEASE that this references AutoRouter
         //Which has an app property
         var oApp = this.app;
         var pathRoutes = _.extend(require('urls'), {
           "default" : require('screens/default')
         });
         pathApply(arguments, pathRoutes, oApp);
       };
    });
    

    那么,我做得更好吗?好吧,如果你只用一两个屏幕做一些非常简单的事情,那么你当然不希望从头开始构建这种设置。但是如果你像我一样,并且你希望能够快速生成新项目,那么拥有一些类似上面两个类的样板允许一个JSON对象告诉应用程序我应该将哪些路由发送到哪些屏幕。然后我可以在适当的位置拥有所有逻辑,允许分离关注点。这就是我认为Backbone非常愉快的原因。

答案 1 :(得分:0)

我对您的问题的理解是,每次进行搜索时都会触发一条路线。

如果您正在这样做,那么使用视图事件哈希(用于捕获和处理视图中发生的事件)进行搜索。不要使用路由。在视图中定义事件哈希并使用回调来处理搜索。

var myAppEventBus = _.extend({},Backbone.Events);

var myAppController = {

  function : search(options) {
    // create an instance of the collection and do a fetch call passing the 
    // search parameters to it.
  
    var searchResultsCollection = new SearchResultsCollection();
  
    // pass search criteria, the success and error callbacks to the fetch
    // method.
    var that = this;
    searchResultsCollection.fetch(
      {
        data:that.options,
        success : function() {
            // Pass the fetched collection object in the trigger call so that
            // it can be
            // received at the event handler call back
            var options = {
              "searchResultsCollection" : that.searchResultsCollection;
            };
            myAppEventBus.trigger("search_event_triggered",options);
     
        },
        error : function() {
          // do the error handling here.
        }
      }
    );
    
     }
};

// Application Router.

var MyAppRouter = Backbone.Router.extend({
  routes : {
    'search/coords=:address&age=:age&rad=:rad': 'search'
  },
  
  search : function(searchParams) {
    // Fetch the query parameters and pass it to the view.
    
    var routeSearchExists = false;
    var searchOptions = {};
    var options = {};
    
    if(searchParams) {
      routeSearchExists = true;
      // If search params exist split and set them accordingly in
      // the searchOptions object.
      
      options.searchOptions = searchOptions;
    }
    
    // Create and render the search view. Pass the searchOptions
    var searchView = new SearchView(options);
    searchView.render();
    
    // Create and render an instance of the search results view.
    var searchResultsView = new SearchResultsView();
    searchResultsView.render();
    
    // If there are search parameters from the route, then do a search.
    if(routeSearchExists) {
      searchView.search();
    }
  }
  

});

// The main view that contains the search component and a container(eg: div) 
// for the search results.
var SearchView = Backbone.View.extend({
  el : "#root_container",
  
  searchOptions : null,
  
  initialize : function(options) {
    // Intialize data required for rendering the view here.
    // When the user searches for data thru routes, it comes down in the 
    // options hash which can then be passed on to the controller.
    if(options.searchOptions) {
      this.searchOptions = options.searchOptions;    
    }
  },
  
  events : {
    "search #search_lnk":"initSearch"
  },
  
  initSearch : function(event) {
    event.preventDefault();
    var searchOptions = {};
    // Fetch the search fields from the form and build the search options.
    myAppController.search(searchOptions);
    
  },
  
  search : function() {
    if(this.searchOptions) {
       myAppController.search(searchOptions);
    }
  }
  
});

// The view to display the search results.
var SearchResultsView = Backbone.View.extend({
  
  
  searchResultsCollection : null;
  
  initialize : function(options) {
    
    // Handling the triggered search event.
    myAppEventBus.on("search_event_triggered",this.render,this);
  },
  
  
  render : function(options) {
    //search results collection is passed as a property in options object.
    if(options.searchResultsCollection)
       //Render your view.
    else
      // Do it the default way of rendering.
      
  }
});

  1. SearchView是包含搜索组件的根视图,以及包含div的容器来保存搜索结果。
  2. SearchResultsView显示搜索结果。
  3. 单击搜索选项时,事件回调(initSearch)将获取输入的搜索数据。
  4. 调用myAppController对象的搜索方法并传递搜索查询。
  5. 创建搜索集合的实例并调用fetch,将搜索查询以及成功和错误回调传递给它。
  6. 成功时,会触发自定义主干事件以及获取的集合。
  7. 调用此事件的回调(SearchResultsView中的呈现方法)。
  8. 回调呈现搜索结果。
  9. 在路由器中加载时,可以创建两个视图的实例(结果视图将为空)并附加到dom。
  10. 如果您希望在网址上搜索多个查询字符串,那么我建议您使用以下路由。 搜索?*的queryString。
  11. 在路由回调中,调用实用程序函数会拆分查询字符串并返回搜索对象并将搜索字符串传递给视图。

答案 2 :(得分:0)

让我们说它不坏,但还有一种更好的方法。

至于现在,你的路由器负责与app astatus的连接URL以及视图和模型控制。第二个可能与路由器分离,因此您需要控制器抽象,但Backbone不会从框中提供“控制器”。

但这不是问题,您可以使用plugin或查看Marionette.js

中的Controller实现

这里的主要想法是正确分配应用程序部分之间的职责:

1)路由器 - 保留路由并使用控制器操作挂接URL

2)控制器 - 管理视图和模型(创建,删除,获取等)

3)查看 - 收听模型和DOM事件并呈现数据

4)模型 - 提供实际数据并处理数据。