这是我的第一个骨干项目,我想知道我是否以最好的方式做事。我的应用程序基本上有两种状态,一种显示搜索框,另一种显示搜索框,其下有一个表格。我的路由器有用于搜索的路由和仅具有搜索视图的初始登录页面。当用户在查询中键入时,路由器将导航到搜索路由,并将表视图添加到页面中。这是我的路由器:
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();
}
});
如您所见,我的视图在索引路由中实例化,然后在搜索时使用相同的视图,除非用户直接转到搜索页面,在这种情况下,视图将在那里实例化。如果这不是很理想,我会感到惊讶,因为检查视图是否已经存在于搜索路线中似乎很笨拙。有没有更好的做事方法?
让我们说它还不错,但有一种更好的方法。
至于现在,您的路由器负责与应用程序状态的连接URL,以及视图和模型控制。第二个可能与路由器分离,因此您将需要控制器抽象,但 Backbone 不提供"开箱即用"的控制器。
但这不是问题,您可以使用插件或查看木偶中的控制器实现.js
这里的主要思想是在应用程序部分之间正确划分职责:
1( 路由器 - 通过控制器操作保留路由和连接 URL
2( 控制器 - 管理视图和模型(创建、删除、获取等(
3( 视图 - 侦听模型和 DOM 事件并渲染数据
4(模型 - 提供实际数据并使用数据。
首先欢迎来到 Backbone。 这是一个可爱的框架,可以让您随心所欲地制作美丽或丑陋的东西。 您的问题是关于视图实例化应该在哪里,就良好实践而言。 当然,在那里这样做似乎有点错误,因为它通过处理 url 路由和视图实例化违反了得墨忒耳定律。
但是视图必须从某个地方运行,对吗? 如果不是路由器,那么在哪里?
所以我有两个回应:
-
如果你的应用程序很简单,你只是想玩骨干,那么你可能会没事的。 很多人让单页应用程序框架使原本简单的应用程序复杂化。 我不是想偷懒,但你现在拥有它的地方是 Backbone 中自然的初学者选择。 如果这是您的情况,请停在这里。
-
如果您想使用骨干网的全部功能来自定义制作框架,请继续阅读。
因此,我的设置旨在能够使用一些样板函数启动一个新项目,并且只创建几个特定于新应用程序的类。 路由处理和所有这些事情对我来说似乎足够低级,它应该只是我不想经常查看的某些配置的一部分。 结果是我的路由器看起来像这样:
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;
});
我永远不必看它,但我确实需要传递一个应用程序实例。 例如:
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如此令人愉快。
我对你的问题的理解是,每次点击搜索时都会触发一条路线。
如果这是您执行此操作的方式,请使用视图事件哈希(用于捕获和处理视图中发生的事件(进行搜索。不要使用路由。在视图中定义事件哈希,并具有回调来处理搜索。
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.
}
});
- SearchView 是包含搜索组件和用于保存搜索结果的容器(如div(的根视图。
- 搜索结果视图显示搜索结果。
- 单击搜索选项时,事件回调 (initSearch( 将获取输入的搜索数据。
- 调用 myAppController 对象上的搜索方法并传递搜索查询。
- 创建搜索集合的实例并调用 fetch,向其传递搜索查询以及成功和错误回调。
- 成功后,将触发自定义主干事件以及提取的集合。
- 调用此事件的回调(搜索结果视图中的呈现方法(。
- 回调呈现搜索结果。
- 在路由器中加载时,可以创建两个视图的实例(结果视图将为空(并将其附加到 dom。
- 如果您希望在 url 上按多个查询字符串进行搜索,那么我建议您使用以下路由。搜索?*查询字符串。
- 在路由回调中,调用实用程序函数,拆分查询字符串并返回搜索对象并将搜索字符串传递给视图。