Backbone Marionette and jQuery plugin (jCarousel) - backbone.js

I am stuck with setting jCarousel jquery plugin inside Backbone Marionette application. With pure Backbone I found a solution using onShow function.
But this does not work in Marionette
Here is the code of Marionette:
$(document).ready(function(){
MyApp = new Backbone.Marionette.Application();
MyApp.addRegions({
TagsRegion: "#mycarousel"
});
MyApp.Tag = Backbone.Model.extend({
});
MyApp.TagCollection = Backbone.Collection.extend({
model: MyApp.Tag,
url: 'json/photos.json'
});
MyApp.TagItemView = Backbone.Marionette.ItemView.extend({
template: "#tag-template",
tagName: 'li'
});
MyApp.TagCollectionView = Backbone.Marionette.CollectionView.extend({
itemView: MyApp.TagItemView,
tagName: 'ul'
});
MyApp.addInitializer(function(options){
var tagCollection = new MyApp.TagCollection();
var tagCollectionView = new MyApp.TagCollectionView({
collection: tagCollection
});
tagCollection.fetch();
MyApp.TagsRegion.show(tagCollectionView);
});
MyApp.start();
});//END jQUERY
</script>
Then I am trying to find a solution how to build in the plugin...
var PluginView = Backbone.View.extend({
el:$('#mycarousel'),
onShow: function(){
this.$el.jcarousel({
scroll: 1,
auto: 13,
wrap: "circular",
size: 5,
initCallback: function mycarousel_initCallback(carousel) {
$('.jcarousel-control a').bind('click', function () {
carousel.scroll($.jcarousel.intval($(this).text()));
return false;
});
},
itemVisibleInCallback: {
onAfterAnimation: function (c, o, i, s) {
i = (i - 1) % $('#mycarousel li').size();
$('.jcarousel-control a').removeClass('active').addClass('inactive');
$('.jcarousel-control a:eq(' + i + ')').removeClass('inactive').addClass('active');
}
}
}); //jcarousel end
} //onShow function end
}); //PluginView End
var plug_view = new PluginView();
plug_view.render();
if (plug_view.onShow){
plug_view.onShow();
};
The problem is that carousel is based on UL with a bunch of li-tags and both the plugin and the marionette generates their own li-tags simultaneously. As a result a have empty carousel-li-tags and marionette-generated-li-tags containing all images and other content to be used inside carousel-li-tags.
I would appreciate if anyone helps to find any solution for this case as well as jquery-plugins in marionette in general.

If you use the Marionette ItemView, there is an onShow method that is fired once the view is rendered to the DOM. Then you would:
MyApp.TagsRegion.show(ItemViewGoesHere);
this will render your item view and once the content is rendered to the DOM, your onShow method will be called and you can render your jQuery plugin to an element within the view.

Related

How to insert an already existing dom element instead of returning html string

Handlebars guide has an example of how to implement helper methods. But how can I render an already existing element instead of getting outerHTML of this element and return it as a string. I need this because of my Ember views setting events on elements and when I getting outerHTML it generates new dom elements, and events of views does not fire UP.
Ok I have backbone View that getting Handlebars precompiled template
SomeView = Backbone.View.extend({
initialise: function(){
this.render();
},
render: function(){
html = HandlebarsTemplates.some_template();
this.el.innerHtml = html;
},
raiseAlert: function(){
alert('Some message');
},
events: {
'click': 'raiseAlert'
}
})
And I have Handlebars helper
Handlebars.registerHelper 'show', function(){
var view = new SomeView({});
var result = view.el.outerHTML;
new Handlebars.SafeString(result)
}
So I want to not use outerHtml but insert view.el instead.

Marionette CollectionView with requireJS: undefined is not a function

I will explain my problem with an example. I can make this piece of code work without any problem (using MarionetteJS v1.6.2):
http://codepen.io/jackocnr/pen/tvqHa
But when I try to use it with requireJs and I put it on the initialize method of a Marionette Controller, I'm Getting the following error:
Uncaught TypeError: undefined is not a function backbone.marionette.js:2089
The Error comes when I define the collection view:
var userListView = new UserListView({
collection: userList
});
I Can't figure out what is happening (this is the same code of the link above, but inside the controller initialize method)
define([
'jquery',
'underscore',
'backbone',
'marionette'
], function($,_,Backbone,Marionette){
var Controller = Backbone.Marionette.Controller.extend({
initialize: function(){
var User = Backbone.Model.extend({});
var UserList = Backbone.Collection.extend({
model: User
});
var UserView = Backbone.Marionette.ItemView.extend({
template: _.template($("#user-template").html())
});
var UserListView = Backbone.Marionette.CollectionView.extend({
tagName: "ul",
itemView: UserView,
initialize: function() {
this.listenTo(this.collection, "add", this.render);
}
});
// instances
var jack = new User({name: "Jack"});
var userList = new UserList(jack);
var userListView = new UserListView({
collection: userList
});
// add to page
$("#user-list").append(userListView.render().el);
$("#add-user").click(function() {
var andy = new User({name: "Andy"});
userList.add(andy);
});
},
});
return Controller;
});
instead of using Backbone.Marionette in main.js shim : { use Marionette
marionette: {
exports: 'Marionette',
deps: ['backbone']
},
Thus while declaring any marionette inheritance juste use Marionette instead of Backbone.Marionette as such
var Controller = Marionette.Controller.extend
var UserView = Marionette.ItemView.extend
var UserListView = Marionette.CollectionView.extend
For some reason the newer version or Marionette.js behave this way. I guest it produce less code.
I have replaced the Marionette 1.6.2 version with the 1.5, and now it works as it does the version without requireJs. So I think it's a release bug or something like that.
Seems to be working fine for me. I made a simple project here.

Backbone 1.0 js events still attached after .html([content])

The problem I am having is click events keep piling up (still attached after changing the view). I have fixed the problem by only having one instance of the view (shown below). I thought backbone got rid of events when the markup is changed. I haven't had this problem with other views.
BROKEN CODE: Click events keep piling up on loadPlayerCard as more views are created.
//Player Thumb View
PgaPlayersApp.PlayerThumbView = Backbone.View.extend({
events: {
'click': 'loadPlayerCard'
},
tagName: 'li',
template: _.template( $('#player_thumb').html()),
render: function()
{
this.$el.html(this.template(this.model.toJSON()));
return this;
},
loadPlayerCard: function()
{
new PlayerCardView({model: this.model}).render();
return false;
}
});
//Router
var Router = Backbone.Router.extend({
routes:{
'': 'loadPlayers'
},
loadPlayers: function()
{
PgaPlayersApp.Players.fetch({reset: true, success: function()
{
//When players is first fetched, we want to render the first player into the card area
new PlayerCardView({model: PgaPlayersApp.Players.first()}).render();
}});
}
});
PgaPlayersApp.Router = new Router();
Backbone.history.start();
FIXED CODE: Code that fixes the problem:
PgaPlayersApp.CurrentPlayerCard = new PlayerCardView();
//Player Thumb View
PgaPlayersApp.PlayerThumbView = Backbone.View.extend({
events: {
'click': 'loadPlayerCard'
},
tagName: 'li',
template: _.template( $('#player_thumb').html()),
render: function()
{
this.$el.html(this.template(this.model.toJSON()));
return this;
},
loadPlayerCard: function()
{
PgaPlayersApp.CurrentPlayerCard.model = this.model;
PgaPlayersApp.CurrentPlayerCard.render();
return false;
}
});
//Router
var Router = Backbone.Router.extend({
routes:{
'': 'loadPlayers'
},
loadPlayers: function()
{
PgaPlayersApp.Players.fetch({reset: true, success: function()
{
//When players is first fetched, we want to render the first player into the card area
PgaPlayersApp.CurrentPlayerCard.model = PgaPlayersApp.Players.first();
PgaPlayersApp.CurrentPlayerCard.render();
}});
}
});
PgaPlayersApp.Router = new Router();
Backbone.history.start();
PlayerCardView (For reference):
var PlayerCardView = PgaPlayersApp.PlayerCardView = Backbone.View.extend({
events: {
'click': 'flipCard'
},
el: '#pga_player_card',
template: _.template( $('#player_card').html()),
render: function()
{
this.$el.html(this.template(this.model.toJSON()));
return this;
},
flipCard: function()
{
this.$("#player_card_container").toggleClass('flip');
}
});
In your router you keep creating new PlayerCardViews:
new PlayerCardView({model: PgaPlayersApp.Players.first()}).render();
All of those views share exactly the same el:
el: '#pga_player_card'
So you keep creating new PlayerCardViews and each one binds to #pga_player_card.
Every time you do that, you bind a brand new view to exactly the same DOM element and each of those views will call delegateEvents to bind the event handlers. Note that delegateEvents binds to el and that jQuery's html method:
removes other constructs such as data and event handlers from child elements before replacing those elements with the new content.
So html does nothing to el but it will remove event handlers from child elements. Consider this simple example with <div id="d"></div>:
$('#d').on('click', function() {
console.log('Before .html');
});
$('#d').html('<p>Where is pancakes house?</p>');
$('#d').on('click', function() {
console.log('After .html');
});
If you then click on #d, you'll see both the before and after messages in the console.
Demo: http://jsfiddle.net/ambiguous/ftJtS/
That simple example is, more or less, equivalent to what you're doing.
You'll have a better time if you:
Put the view inside #pga_player_card and let the router do $('#pga_player_card').append(view.render().el).
Keep track of the view that's already there and view.remove() it before adding the new one.
Avoid trying to reuse DOM elements for multiple view instances and avoid trying to reuse views, neither is worth the hassle.

backbone render not rendering select tags

I've got a simple div into which I'd like backbone to render a select box and options from my server.
The options seem to render just fine, but the select box does not. I'm sure it's a simple tweak, but can't seem to find it.
I created a simplified fiddle for it: http://jsfiddle.net/thunderrabbit/BNZY3/
The HTML
<div id="where_fields"></div>
The script I'm using uses fetch() to get the data. The Fiddle above hardcodes the data, but the issue is the same.
(function($){
var Field = Backbone.Model.extend();
var UnitFields = Backbone.Collection.extend({
url: '/<?php echo CONFIG_ADMIN_DIR; ?>/api/fieldnames/units',
model: Field
});
var BuildingFields = Backbone.Collection.extend({
url: '/<?php echo CONFIG_ADMIN_DIR; ?>/api/fieldnames/buildings',
model: Field
});
var FieldView = Backbone.View.extend({
tagName: "option",
initialize: function(){
_.bindAll(this, 'render');
},
events: {
"click":"clicked"
},
clicked: function(e) {
var data_type = this.model.get("DATA_TYPE");
if(data_type == "varchar") {
console.log("it's a varchar");
}
if(data_type == "int") {
console.log("it's an int");
}
},
render: function(){
$(this.el).attr('value', this.model.get('COLUMN_NAME')).html(this.model.get('display_name'));
return this;
}
});
var FieldsView = Backbone.View.extend({
tagName: "select",
el: $('#where_fields'),
initialize: function(){
_.bindAll(this, 'render', 'renderItem');
this.collection.bind('reset', this.render);
},
renderItem: function(model) {
console.log('rendr item');
var fieldView = new FieldView({model:model});
fieldView.render();
$(this.el).append(fieldView.el);
},
render: function(){
console.log('rendr');
this.collection.each(this.renderItem);
return this;
}
});
var units_fields = new UnitFields();
var buildings_fields = new BuildingFields();
var unitsView = new FieldsView({collection: units_fields});
var buildingsView = new FieldsView({collection: buildings_fields});
units_fields.fetch();
buildings_fields.fetch();
})(jQuery);
Why is my backbone script not rendering the select tags?
You have both tagName and el attributes in your FieldsView class. You don't need both. Use tagName if you want to render a view detached from the DOM and then backbone will use that tag instead of the default of div. However, in your render(), you don't ever actually get a select tag involved. $(this.el) is your #where_fields div and you just append fieldView.el, which is an option element. That's why there is no select element. Some quick tips:
use this.$el as a more efficient shorthand for $(this.el)
It's preferable to keep your view loosely coupled from the DOM, so el: $('#where_fields') is not as clean a design as rendering an element detached from the DOM and letting other code decide where exactly in the existing DOM it should be attached.
So you should remove your el properly, set tagName to select if you like, then your render() method will be doing what you want with is appending options to a select tag, then move the actual code to append your view's rendered el to the #where_fields div out of the view into your router perhaps.

How to create a reusable backbone/backbone.marionette component such a datagrid?

I am pretty new to Backbone and Backbone.Marionette. I succeed to create a simple page with a sort of datagrid that allows me paging (first, previous, next, last pages), quick searching (triggered at each time a key is pressed), choosing the number of items shown on page (5, 10, all, ...)
Now that I have something working, I tried to improve that and to make these features as a sort of reusable component but I do not know exactly the way to follow. I do not know how to start to refine the work already done.
For example, I want to be able to change the collection/model manage by the datagrid without rewriting everything. This is where I am not confident how to do that and it is probably due to a lack of knowledge. So your inputs and advice to go further will be really appreciated and welcomed.
// 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();
});
Edit 1:
Based on the answer given and my own ideas, I wrote a first draft of a solution. I did not succeed to write a real reusable component but I have a solution that consolidate my code. Some part need to be refactored and improved. There are also some pitfals that I want to solve in a later refactoring.
To add some context, the application is written with Rails as the backend. So there is my javascript folder structure
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
First, the application.js start. The assets pipelines from Rails 3.2 will prepare the dependencies as expected:
//= 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();
});
Now, we can prepare the admin part to 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();
});
Then we can define the datagrid part:
// 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;
});
We are done on the structural code. Now we can go to one of the controller. For example, the 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");
}
});
});
});
});
And finally the views for the previous controller:
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;
}
});
});
Remark: The subtheme controller and view files contains exactly the same kind of codes. Only templates and kind of stuffs differ.
The grid layout in HAML compiled through Rails assets pipeline looks like:
.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= ">>"
As you can see, there is quite a lot of repetition. I wanted to have quick search and pagination on top and bottom of my grid. At the moment, the simplest way to do that is to duplicate the code. I will change that later when I will find how to do that.
The template for table that shows the themes:
%table.table.table-striped
%thead
%tr
%th= "Id"
%th= "Name"
%tbody#themes
Quite simple and nothing special to say. At this time, the headers are hardcoded !
And finally, the item view template to show a theme:
%td= this.id
%td= this.name
This template is realy simple.
I am in a situation were that is pretty well working. For example, when I click on other links to change the collection shown, the quick search fields and kind stuff like that are not reinitialized. For that, I wanted to add a sort of state management to keep trace of the collection state and when come back to an already shown collection, I want to show it as it was previously.
I am sure that my solution is not perfect and could be refactored a lot. I also probably did a lot of "newbie" mistakes. So feel free to challenge my proposition. I try to learn and improve my solution and hope it will help someone to do something like that.
Well I'm not a big expert but that's how I did it, using Marionette and Requirejs:
a) I created a generic Grid Layout wiew that is called by my approuter with some parameter like collection, cols config (I render the table head with a each cycle) and a row view:
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) in my Grid Layout I render the various pieces waiting for the onShow event:
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) my generic Table View take the cols and the itemView to render as parameter like this:
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);
},
That's just a general idea, I don't think it's the best way to do it but I didn't find a better solution yet, hope to give you at least some hints :)
I highly recommend the backgrid component which is extensible and reusable out of the box.
Only at version 0.2.6 - but good following and its pretty slick

Resources