我是Backbone和Backbone.Marionette的新手。我成功创建了一个带有一种数据网格的简单页面,允许我分页(第一页,上一页,下一页,最后一页),快速搜索(每按一次键时触发),选择页面上显示的项目数(5 ,10,全部,...)
现在我有一些工作,我试图改进它,并将这些功能作为一种可重用的组件,但我不知道确切的方法。我不知道如何开始完善已经完成的工作。
例如,我希望能够通过datagrid更改集合/模型管理,而无需重写所有内容。这是我不知道如何做到这一点,这可能是由于缺乏知识。因此,您的意见和建议将得到更多的欢迎和欢迎。
// JST and HAML Assets is used for the templating pre-compilation
Backbone.Marionette.Renderer.render = function(template, data) {
if (!JST[template]) {
throw "Template '" + template + "' not found!";
}
return JST[template](data);
};
window.MyApp = new Backbone.Marionette.Application();
MyApp.addRegions({
content: ".content-box"
});
MyApp.Datagrid = (function() {
var Datagrid, ItemPerPageView, Layout, PagerView, QuickSearchView, Theme, ThemeView, Themes, ThemesView;
Datagrid = {};
Layout = Backbone.Marionette.Layout.extend({
template: "layouts/grid",
regions: {
grid: "#grid",
quickSearch: "#quickSearch",
itemPerPage: "#itemPerPage",
pager: ".pager"
}
});
Theme = Backbone.Model.extend();
Themes = Backbone.ExtendedCollection.paginatedCollection.extend({
url: "/themes",
model: Theme,
initialize: function() {
var _this = this;
MyApp.vent.on("quickSearch:term", function(term) {
_this.quickSearch(term);
});
MyApp.vent.on("itemPerPage:count", function(count) {
_this.perPage(count);
});
MyApp.vent.on("pager:previous", function() {
_this.previous();
});
MyApp.vent.on("pager:next", function() {
_this.next();
});
MyApp.vent.on("pager:first", function() {
_this.first();
});
MyApp.vent.on("pager:last", function() {
_this.last();
});
}
});
ThemeView = Backbone.Marionette.ItemView.extend({
tagName: "tr",
template: "theme",
model: Theme,
events: {
"click span": "edit",
"blur input": "save"
},
edit: function(event) {
var id, span;
id = this.model.get("id");
span = $("span", this.el).hide();
$("input", this.el).show().focus().val(span.text());
},
save: function(event) {
var id, input, span;
id = this.model.get("id");
span = $("span", this.el).show();
input = $("input", this.el).hide();
if (this.model.get("name") !== input.val()) {
this.model.set("name", input.val());
this.model.save();
}
span.text(this.model.get("name"));
}
});
ThemesView = Backbone.Marionette.CompositeView.extend({
template: "index",
model: Theme,
itemView: ThemeView,
collection: Themes,
itemViewContainer: "#themes",
serializeData: function() {
return this.data;
}
});
QuickSearchView = Backbone.Marionette.View.extend({
el: "#quickSearch",
events: {
"keyup input": "search"
},
search: function(event) {
var searchTerm;
searchTerm = this.$("input").val().trim();
MyApp.vent.trigger("quickSearch:term", searchTerm);
}
});
ItemPerPageView = Backbone.Marionette.View.extend({
el: "#itemPerPage",
events: {
"change select": "count"
},
count: function(event) {
var count;
count = this.$("select").val();
MyApp.vent.trigger("itemPerPage:count", count);
}
});
PagerView = Backbone.Marionette.View.extend({
el: ".pager",
events: {
"click #next": "next",
"click #previous": "previous",
"click #first": "first",
"click #last": "last"
},
first: function(event) {
MyApp.vent.trigger("pager:first");
},
last: function(event) {
MyApp.vent.trigger("pager:last");
},
next: function(event) {
MyApp.vent.trigger("pager:next");
},
previous: function(event) {
MyApp.vent.trigger("pager:previous");
}
});
Datagrid.initializeLayout = function() {
var collection;
Datagrid.layout = new Layout();
Datagrid.layout.on("show", function() {
MyApp.vent.trigger("layout:rendered");
});
MyApp.content.show(Datagrid.layout);
collection = new Themes();
collection.fetch();
collection.on("reset", function() {
return Datagrid.layout.grid.show(new ThemesView({
collection: collection
}));
});
};
MyApp.vent.on("layout:rendered", function() {
var itemPerPageView, pagerView, quickSearchView;
quickSearchView = new QuickSearchView();
Datagrid.layout.quickSearch.attachView(quickSearchView);
itemPerPageView = new ItemPerPageView();
Datagrid.layout.itemPerPage.attachView(itemPerPageView);
pagerView = new PagerView();
Datagrid.layout.pager.attachView(pagerView);
});
return Datagrid;
})();
MyApp.addInitializer(function() {
MyApp.Datagrid.initializeLayout();
});
$(document).ready(function() {
return MyApp.start();
});
修改1:
根据给出的答案和我自己的想法,我写了一个解决方案的初稿。我没有成功编写一个真正的可重用组件,但我有一个解决方案来整合我的代码。有些部分需要重构和改进。我想在稍后的重构中解决一些陷阱问题。
要添加一些上下文,应用程序将使用Rails作为后端编写。所以有我的javascript文件夹结构
assets
|--javascripts
|--application.js
|--admin
|--admin.js
|--admin.layout.js
|--subthemes
|--admin.subtheme.controller.js
|--admin.subtheme.view.js
|--themes
|--admin.theme.controller.js
|--admin.theme.view.js
|--templates
|--admin
|--subthemes
|--index.hamlc
|--subtheme.hamlc
|--themes
|--index.hamlc
|--theme.hamlc
|--layouts
|--grid.hamlc
首先,application.js启动。 Rails 3.2中的资产管道将按预期准备依赖项:
//= require underscore
//= require backbone
//= require backbone.marionette
//= require_tree ./lib/backbone
//= require hamlcoffee
//= require i18n
//= require i18n/translations
//= require_tree ../templates/
//= require_tree ./admin
//= require_tree ./admin/theme
//= require_tree ./admin/subtheme
I18n.defaultLocale = "en";
Backbone.Marionette.Renderer.render = function(template, data) {
if (!JST[template]) {
throw "Template '" + template + "' not found!";
}
return JST[template](data);
};
$(document).ready(function() {
return MyApp.start();
});
现在,我们可以准备管理部分开始:
var AdminRouter, TempView;
// Create the application for admin part
MyApp.Admin = new Backbone.Marionette.Application();
// Define a router to handle the grid collection type change
AdminRouter = Backbone.Marionette.AppRouter.extend({
initialize: function() {
var _this = this;
// Route quite generic to easily change the data in the grid
this.route(/^admin\/(.*?)$/, "changeCollection");
// Manage event to handle the navigation on client side
MyApp.Admin.vent.on("admin:navigate", function(link) {
_this.navigate(link, {
trigger: true
});
});
},
// Trigger an event to change the collection if one exist for the URL
changeCollection: function(collectionName) {
MyApp.Admin.vent.trigger("grid:collection:change", collectionName);
}
});
// Side menu that allows changing the collection in the data grid
SideMenuView = Backbone.Marionette.View.extend({
el: ".side-menu",
events: {
"click a": "handleClick"
},
// Prevent the normal behavior on the link click
handleClick: function(event) {
event.preventDefault();
MyApp.Admin.vent.trigger("admin:navigate", $(event.target).attr("href"));
}
});
// Add the initializer to the main application to prepare the admin part (grid)
MyApp.addInitializer(function() {
new SideMenuView();
new AdminRouter();
Backbone.history.start({
pushState: true
});
MyApp.Admin.start();
});
然后我们可以定义datagrid部分:
// This the grid layout module in the admin namespace
MyApp.Admin.module("GridLayout", function(GridLayout, Admin, Backbone, Marionette, $, _) {
var ItemPageSelectorView, Layout, PagerView, QuickSearchView;
// The quick search view handle the related fields to do the quick search
QuickSearchView = Backbone.Marionette.View.extend({
el: ".gridQuickSearch",
events: {
"keyup input": "search"
},
// Get the field content and trigger an event with it
search: function(event) {
var searchTerm;
searchTerm = $(event.target).val().trim();
$("input", this.$el).val(searchTerm);
Admin.vent.trigger("grid:quickSearch:term", searchTerm);
}
});
// The item page selecto handle the choice of how many rows should be displayed per page
ItemPageSelectorView = Backbone.Marionette.View.extend({
el: ".gridItemPageSelector",
events: {
"change select": "count"
},
// Get the number of items per page that should be displayed
count: function(event) {
var count;
count = $(event.target).val();
$("select", this.$el).val(count);
Admin.vent.trigger("grid:itemPageSelector:count", count);
}
});
// The pager view manage the view components to change the page shown in the data grid
PagerView = Backbone.Marionette.View.extend({
el: ".gridPager",
events: {
"click #next": "next",
"click #previous": "previous",
"click #first": "first",
"click #last": "last",
"click #page": "page"
},
//
// The following functions triggers events to go to the right pages
//
first: function(event) {
Admin.vent.trigger("grid:pager:first");
},
previous: function(event) {
Admin.vent.trigger("grid:pager:previous");
},
page: function(event) {
Admin.vent.trigger("grid:pager:page");
},
next: function(event) {
Admin.vent.trigger("grid:pager:next");
},
last: function(event) {
Admin.vent.trigger("grid:pager:last");
}
});
// The grid layout with the regions to display the different part of the data grid
Layout = Backbone.Marionette.Layout.extend({
template: "layouts/grid",
regions: {
gridTable: "#gridTable",
gridQuickSearch: ".gridQuickSearch",
gridItemPageSelector: ".gridItemPageSelector",
gridPager: ".gridPager"
}
});
// Once the layout is rendered, the different views are attached to the right regions
Admin.vent.on("grid:layout:rendered", function() {
var itemPageSelectorView, pagerView, quickSearchView;
quickSearchView = new QuickSearchView();
Admin.gridLayout.gridQuickSearch.attachView(quickSearchView);
itemPageSelectorView = new ItemPageSelectorView();
Admin.gridLayout.gridItemPageSelector.attachView(itemPageSelectorView);
pagerView = new PagerView();
Admin.gridLayout.gridPager.attachView(pagerView);
});
// Initializer to do at the application start
GridLayout.addInitializer(function() {
Admin.addRegions({
content: ".content-box"
});
Admin.gridLayout = new Layout();
// Trigger the rendered event when the grid layout is shown
Admin.gridLayout.on("show", function() {
Admin.vent.trigger("grid:layout:rendered");
});
// Manage the collection data change
Admin.vent.on("grid:collection:change", function(collectionName) {
// Close the previous view in the grid table region
Admin.gridLayout.gridTable.close();
// Trigger an event to fetch the collection
Admin.vent.trigger("" + collectionName + ":collection:fetch");
// Show the grid layout if not already done
if (!this.shown) {
this.shown = true;
Admin.content.show(Admin.gridLayout);
}
});
});
return GridLayout;
});
我们完成了结构代码。现在我们可以去一个控制器。例如,ThemeController:
MyApp.Admin.module("ThemeController", function(ThemeController, Admin, Backbone, Marionette, $, _) {
// Define the model to use in the collection
ThemeController.Theme = Backbone.Model.extend();
// Define the collection with the related url on the server. The collection extends a paginated collection that has the methods to manage the quick search and the pagination
ThemeController.Themes = Backbone.ExtendedCollection.paginatedCollection.extend({
url: "/admin/themes",
model: ThemeController.Theme,
initialize: function() {
var _this = this;
//
// The following functions handle the events for the quick search and pagination
//
Admin.vent.on("grid:quickSearch:term", function(term) {
_this.quickSearch(term);
});
Admin.vent.on("grid:itemPageSelector:count", function(count) {
_this.perPage(count);
});
Admin.vent.on("grid:pager:previous", function() {
_this.previous();
});
Admin.vent.on("grid:pager:next", function() {
_this.next();
});
Admin.vent.on("grid:pager:first", function() {
_this.first();
});
return MyApp.Admin.vent.on("grid:collection:fetched", function() {
Admin.gridLayout.gridTable.show(new Admin.ThemeView.Table({
collection: _this
}));
});
}
});
// At the application initilization, we need to be sure this controller can
// handle the event to fetch the data from the server
Admin.addInitializer(function() {
Admin.vent.on("themes:collection:fetch", function() {
ThemeController.themes = new ThemeController.Themes();
// Once the data are fetched from the server, trigger an event to display them
ThemeController.themes.fetch({
success: function() {
Admin.vent.trigger("grid:collection:fetched");
}
});
});
});
});
最后是前一个控制器的观点:
MyApp.Admin.module("ThemeView", function(ThemeView, Admin, Backbone, Marionette, $, _) {
// The view to show one item in a row of the data grid
ThemeView.Item = Backbone.Marionette.ItemView.extend({
tagName: "tr",
template: "admin/themes/theme",
model: Admin.ThemeController.Theme
});
// The view to show the collection of item
ThemeView.Table = Backbone.Marionette.CompositeView.extend({
template: "admin/themes/index",
model: Admin.ThemeController.Theme,
itemView: ThemeView.Item,
collection: Admin.ThemeController.Themes,
itemViewContainer: "#themes",
// ! I was force to add this to have data in the original format that is used by my templates !
serializeData: function() {
return this.data;
}
});
});
备注:子主题控制器和视图文件包含完全相同类型的代码。只有模板和类型的东西不同。
通过Rails资产管道编译的HAML中的网格布局如下所示:
.gridPager
%button#first= "<<"
%button#previous= "<"
%button#next= ">"
%button#last= ">>"
%span.gridItemPageSelector= "Item per page"
%select
%option= 5
%option= 10
%option{"value" => -1}= "All"
%span.gridQuickSearch= "Quick search:"
%input#gridSearchTerm{"type" => "text"}
#gridTable
%span.gridItemPageSelector= "Item per page"
%select
%option= 5
%option= 10
%option{"value" => -1}= "All"
%span.gridQuickSearch= "Quick search:"
%input#gridSearchTerm{"type" => "text"}
.gridPager
%button#first= "<<"
%button#previous= "<"
%button#next= ">"
%button#last= ">>"
正如你所看到的,有很多重复。我想在网格的顶部和底部快速搜索和分页。目前,最简单的方法是复制代码。我将在以后找到如何做到的时候改变它。
显示主题的表格模板:
%table.table.table-striped
%thead
%tr
%th= "Id"
%th= "Name"
%tbody#themes
非常简单,没什么好说的。此时,标题是硬编码的!
最后,项目视图模板显示主题:
%td= this.id
%td= this.name
这个模板非常简单。
我处于一种非常好的工作状态。例如,当我点击其他链接更改显示的集合时,快速搜索字段和类似的东西不会重新初始化。为此,我想添加一种状态管理来跟踪收集状态,当回到已经显示的集合时,我想像以前一样显示它。
我确信我的解决方案并不完美,可以进行大量重构。我也可能犯了很多“新手”的错误。所以随意挑战我的主张。我尝试学习并改进我的解决方案,并希望它能帮助某人做类似的事情。
答案 0 :(得分:2)
嗯,我不是一个大专家,但我就是这样做的,使用Marionette和Requirejs:
a)我创建了一个通用的Grid Layout wiew,由我的approuter调用,带有一些参数,如collection,cols config(我用每个循环渲染表头)和一个行视图:
showUsers: function(){
require(['views/GridGen','collections/user_collection'], function(Grid, UserCollection){
var Users = new UserCollection();
App.grid = new Grid({collection: Users ,
rowView: 'rowUser',
cols_config: App.tables.users});
App.page.show(App.grid);
});
},
b)在我的网格布局中,我渲染了等待onShow事件的各个部分:
var Grid = Backbone.Marionette.Layout.extend({
regions: {
top_controls: "#top_controls",
table_view: "#table_view",
pagination_controls: "#pagination_controls",
bottom_controls: "#bottom_controls",
},
onShow: function(){
this.renderTable(this.collection);
},
renderTable: function(collection){
collection.fetch({success:function(){
require(['views/'+self.options.rowView+'.js'],function(iView){
var vista = new View({collection: collection, itemView: iView, thead: self.options.cols_config});
App.grid.table_view.show(vista);
self.renderPagination(collection);
collection.pager();
});
}});
}
c)我的通用表视图将cols和itemView呈现为如下参数:
var View = Backbone.Marionette.CompositeView.extend({
initialize: function(){
this.itemView = this.options.itemView;
},
serializeData: function(){
var data = Backbone.Marionette.ItemView.prototype.serializeData.apply(this, arguments);
data.thead = this.options.thead;
return data;
},
appendHtml: function(collectionView, itemView, index){
collectionView.$("tbody").append(itemView.el);
},
这只是一个普遍的想法,我不认为这是最好的方法,但我还没有找到更好的解决方案,希望至少给你一些提示:)
答案 1 :(得分:1)
我强烈推荐可扩展且可重复使用的backgrid component。
只有版本0.2.6 - 但很好的跟随和漂亮的光滑