I have a Marionette ItemView like this:
List.Chart = Backbone.Marionette.ItemView.extend({
template: "#chart-template",
className: "block container-fluid first",
onRender: function () {
// Do a few things
},
});
The template has a <select id="filter"> tag with a few options in it. I would like to access the value of the <select> tag when a user clicks it and there has been a change. I'm new to Marionette. I tried a few ways but none got me the value. Thanks.
You will not want to override render in your marionette view, instead you should take advantage of onRender
http://marionettejs.com/docs/marionette.itemview.html#render--onrender-event
As far as knowing when a user has click or changed the input you will want to take advantage of the events hash.
http://marionettejs.com/docs/marionette.view.html#viewevents
Which actually comes from backbone.
So it will look like this:
List.Chart = Backbone.Marionette.ItemView.extend({
template: "#chart-template",
className: "block container-fluid first",
events: {
"click #filter": "doSomething",
"change #filter": "doSomething"
},
doSomething: function() {}
});
For extra points you should DRY up the event binding and take advantage of the #ui interpolation that backbone.marionette gives you for free.
List.Chart = Backbone.Marionette.ItemView.extend({
template: "#chart-template",
className: "block container-fluid first",
ui: {
"filter": "#filter"
},
events: {
"click #ui.filter": "doSomething",
"change #ui.filter": "doSomething"
}
});
Related
In my backbone application, I have a model that looks a little like this,
{
"id" : 145,
"name" : "Group Number 1",
"information" : "Some kind of blurb about group number 1",
"members" : {[
"id" : 1,
"first_name" : "John",
"last_name" : "Doe",
"email" : "johndoe#goog.ecom"
]}
}
Now if I run this.model.get('members').add(newUser) a new user gets added to the members collection within my model - however it does not fire a change event, why is this? Buy yet if I change the name of the model, then a change event is fired?
All this is done with a view that looks like this,
Individual model view
Views.OrganisationView = Backbone.View.extend({
tagName: 'div',
className:'group group--panel col-sm-3',
template : _.template( $('#tpl-single-group').html() ),
events: {
"click a[data-type=organisation], button[data-type=organisation]" : "edit",
"click .js-delete-group" : "removeOrganisation",
},
initialize: function() {
this.model.on("error", function(model, xhr, options){
console.log(model, xhr, options);
console.log(this);
});
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'destroy', this.removeView);
},
render: function() {
this.$el.html( this.template({
group: this.model.toJSON()
}));
return this;
},
removeView: function() {
this.remove();
},
removeOrganisation: function(e) {
this.model.destory();
this.remove();
},
edit: function(e) {
e.preventDefault();
Routes.Application.navigate('/organisation/edit/' + this.model.get('id'), { trigger: false } );
var editClient = new Views.OrganisastionEditView({
model: this.model
});
}
});
The second confusing thing that a request event gets thrown, (makes sense seen as though I am saving the model, but an error event gets thrown as well, but there are no errors the xhr and I am not currently validating the model?
Here is how I am saving the user to members collection in my model,
var member = new Pops.Models.User({ id: element.data('id') });
member.fetch({
success:function() {
self.model.get('members').add(member);
var model = self.model;
self.$('.search').hide();
self.$('button').show();
var projectMember = new Pops.Views.UserInitialsWithAdmin({
model: member
});
self.model.save({validate:false});
self.$('.search').parent().append( projectMember.render().el );
self.$('.search').remove();
}
});
(I'm assuming the first bit of code you've given is just a guideline of what a plain JSON representation of your model would look like, and that members is a real Collection with an add method available.)
In answer to the first question: change events are only fired when changing a model attribute using set. In your case, you're adding to the collection stored in the members attribute, but the members attribute still contains a reference to the same collection it did before, which means from Backbone's perspective this attribute has not changed. I would suggest attaching listeners directly to the members collection. Also see How can I "bubble up" events on nested Backbone collections?.
In general nesting models in Backbone is not straightforward, as Jeremy Ashkenas has pointed out. It's often better to keep models flat and store references to related models as arrays of ids, which can then be fetched as necessary.
I use backbone.boilerplate for creating a simple application.
I want to create module that can show collections of sites. Each sites has id and title attributes (example [{ id: 1, title: "github.com" }, { id: 2, title: "facebook.com" }].
My router:
routes: {
"": "index",
"sites": "sites"
},
sites: function () {
require(['modules/sites'], function (Sites) {
var layout = new Sites.Views.Layout();
app.layout.setView('#content', layout);
});
}
So, my sites module has layout, which do this:
Sites.Views.Layout = Backbone.Layout.extend({
template: "sites/layout",
className: 'container-fluid',
initialize: function () {
_.bindAll(this);
this.collection = new Sites.Collections.Sites();
this.collection.fetch({
success: this.render
});
},
beforeRender: function () {
var siteList = new Sites.Views.SiteList({
collection: this.collection
});
this.setView('.content', siteList);
},
});
Sites.Views.SiteList = Backbone.View.extend({
template: 'sites/list',
beforeRender: function () {
this.collection.each(function (model) {
var view = new Sites.Views.SiteItem({
model: model
});
this.insertView('tbody', view);
}, this);
}
});
Sites.Views.SiteItem = Backbone.View.extend({
template: 'sites/item',
tagName: 'tr',
serialize: function () {
return {
title: this.model.get('title')
};
}
});
ok. and now my question: help me please to choose best way to render one site view when user click on element of collection. I want that it is works like gmail: one screen for all letters and all screen for one letter when it choosed. Maybe you have link with example of similar application. I am waiting for your advices.
Looking at your pastebin code it seems like you have a basic understanding of Backbone, which is certainly all you need to get started.
That being said, you might find this article/tutorial helpful. It walks through the process of building inter-connected views (in the tutorial they are related <select> elements) which use AJAX to update their values:
http://blog.shinetech.com/2011/07/25/cascading-select-boxes-with-backbone-js/
I have a parent view ProductListView containing multiple child views ProductView in a multi-step wizard. When a user click on a ProductView, its model's id should be stored somewhere (possibly in an array) so that it can be sent back to the server side for processing.
Problem: Where should I store the id of the ProductView that has been clicked by the user? I tried storing it in its parent view ProductListView but cannot seem to access the array selectedProducts in the parent view from the child view ProductView.
Is this the correct approach? How should this be done?
Model
ProductCollection = Backbone.Collection.extend({
model: Product,
url: '/wizard'
});
Parent View
ProductListView = Backbone.View.extend({
el: '#photo_list',
selectedProducts: {}, // STORING SELECTED PRODUCTS IN THIS ARRAY
initialize: function() {
this.collection.bind('reset', this.render, this);
},
render: function() {
this.collection.each(function(product, index){
$(this.el).append(new ProductView({ model: product }).render().el);
}, this);
return this;
}
});
Child View
ProductView = Backbone.View.extend({
tagname: 'div',
className: 'photo_box',
events: {
'click': 'toggleSelection'
},
template: _.template($('#tpl-PhotoListItemView').html()),
render: function() {
this.$el.html(this.template( this.model.toJSON() ));
return this;
},
// ADDS ITS MODEL'S ID TO ARRAY
toggleSelection: function() {
this.parent.selectedProducts.push(this.model.id);
console.log(this.parent.selectedProducts);
}
});
I don't think parent is a property of a backbone View type, and you haven't defined it, so there's no way this line is going to work:
this.parent.selectedProducts.push(this.model.id);
It seems like the correct approach would be to add a selected property to the Product model; toggle that property in the click handler. Then, when it's time to submit to the server, collect the IDs by filtering the Products collection for selected items (underscore.js included with Backbone makes this easy).
Why not try to keep selected information, directly in model. So, you will be easily tracking change state of selected using events, and use that information on further wizard steps?
toggleSelection: function () {
this.model.set({ selected: true });
}
I am writing my first Backbone.js application and I am having some trouble figuring out the best way to program it. I have 2 main views:
Shows an index of all my models.
Shows a specific model for editing.
But #2 has many different 'modules' like I can edit the 'news' section, or 'about' section etc...
All these modules are in a navigation bar.
That navigation bar is hidden when I am displaying view # 1 (index of all models). It is visible in view # 2(a specific model) in order to navigate between different modules.
I have routes setup like this:
routes: {
'', 'index',
'communities': 'index',
'communities/:id': 'main',
'communities/:id/news', 'news',
'communities/:id/about', 'about'
},
So my question is, when 'news' or 'about' action is called, do I add a navigation bar in each method? Isn't that redundant? I am going to have like 8-10 different modules, add navigation bar each time seems very repetitive. Is there a better way?
The only time I want the navigation bar to be hidden is when showing index.
I came across this same problem when I created my first somewhat complex Backbone app. Along with your concern of redundant code, I was concerned about events bound to my navbar that may not get unbound as the navigation bar changed. To solve the problem, I wound up creating a view hierarchy, with one manager view managing the navigation bar a whole, and separate views for each type of navigation menu I wanted to display, which would be passed to the manager view to render to the page.
Here's an example of my implementation.
Before we start, here is a close function I added to Backbone's View prototype which unbinds events and removes the view
Backbone.View.prototype.close = function() {
if(this.beforeClose) { this.beforeClose(); }
this.remove();
this.unbind();
}
First, here is my Manager View. Its render function closes whatever menu is currently displayed and replaces it with the one passed to it as view. While slightly redundant, I created an explicit empty function to make my router code easier to understand.
var App.Views.SubNavBar = Backbone.View.extend({
currentView: null,
el: '#subnav-wrap',
render: function(view) {
if(this.currentView) { this.currentView.close(); }
this.currentView = view;
this.$el.html(view.el);
},
empty: function() {
if(this.currentView) { this.currentView.close(); }
this.currentView = null;
}
});
Second, here is a base view that all of my specific navigation menu views extend. Since they will all have the same tagName, className, id, and initialize and render functions, this keeps repetition to a minimum
var App.Views.SubNavBase = Backbone.View.extend({
tagName: 'ul',
className: 'nav nav-pills',
id: 'subnav',
template: _.template($('#tmpl-subnav').html(),
initialize: function() {
if(this.setLinks) { this.setLinks(); }
this.render();
},
render: function() {
this.$el.html(this.template({links:this.links}));
return this;
}
});
Here is an example of a view for a specific navigation menu. You can see that all I need to do is define the links I want to appear in the menu. When I instantiate this view, the functions of SubNavBase will handle populating the view with the required HTML. Note that I also have some events attached to this view.
var App.Views.Projects.DisplayNav = App.Views.SubNavBase.extend({
setLinks: function() {
this.links = {
'Edit Project': {
icon: 'edit',
class: 'menu-edit',
href: '#projects/'+this.model.get('id')+'/edit'
},
'Add Group': {
icon: 'plus',
class: 'menu-add-group',
href: '#projects/'+this.model.get('id')+'/groups/new'
},
'Delete Project': {
icon: 'trash',
class: 'menu-delete',
href: '#'
}
}
},
events: {
'click a.menu-delete' : 'delete'
},
delete: function(e) {
e.preventDefault();
// here goes my code to delete a project model
}
});
Now, here is the underscore.js template I use to turn the links object above into a list of <li> elements. Note that I use <# instead of <% for my templates since this is a rails app and rails already uses <%
<script type="text/template" id="tmpl-subnav">
<# _.each(links,function(link, title) { #>
<li>
<a href="<#= link.href #>" class="<#= link.class #>">
<i class="icon-<#= link.icon #>"></i>
<#= link.title #>
</a>
</li>
<# }); #>
</script>
Finally, to put it all together, here is an example Router function that creates and renders the nav menu. The steps that occur are as follows:
App.Views.Projects.DisplayNav gets passed a model and populates its this.el with the corresponding HTML, as determined by the underscore.js template
App.SubNavBar has its render function called with the new menu view
App.SubNavBar checks to see if there is currently another menu in the navigation bar; if so, it calls its view's close() function
App.SubNavBar finally appends the passed view's HTML to itself, maintaining a reference to the view for later use
I've included only the relevant parts of the router code
var App.Routers.Projects = Backbone.Router.extend({
routes: {
'projects/:id' : 'display'
},
display: function(id) {
var p = projects.get(id);
var subnav = new App.Views.Projects.DisplayNav({model:p})
App.SubNavManager.render(subnav); // App.SubNavManager is an instance of App.Views.SubNavBar
}
});
The benefit to all of this is that I can now attach events to my menu-specific views, and the manager view will take care of unbinding them if the user navigates to different content and the menu changes.
Of course, there are many other patterns you can use to handle navigation menus, but hopefully this will help you on the path.
Try this:
routes: {
'', 'index',
'communities': 'index',
'communities/:id': 'main',
'communities/:id/:section': 'openSection'
},
openSection : function(id, section){
if( section ){
this.addNavigationBar();
}
switch( section ){
case 'news' :
this.news();
break;
case 'about' :
this.about();
break;
default:
this.main();
}
}
If your url contents a section you will add the navigation bar and then call you normal method as you have.
I have two different ways of displaying my models on my site, and I handle them with different views of course.
However, I want to be able to connect these views somehow, such that when an event happens on one of the views on a specific model, it can also trigger an event on the other view.
For sake of simplicity, let's say that I have a collection and that I represent the collection with two views that generate identical ul lists. (In reality, the views are of course different).
HTML
<ul class="view-a">
<li class="subview-a">Model 1</li>
<li class="subview-a">Model 2</li>
</ul>
<ul class="view-b">
<li class="subview-b">Model 1</li>
<li class="subview-b">Model 2</li>
</ul>
Backbone
viewA = Backbone.View.extend({
tagName: 'ul',
className: 'view-a',
});
viewB = Backbone.View.extend({
tagName: 'ul',
className: 'view-b',
});
subViewA = Backbone.View.extend({
tagName: 'li',
className: 'subview-a',
events: {
'mouseover':'over',
},
over: function() {
console.log('mouse over on A');
}
});
subViewB = Backbone.View.extend({
tagName: 'li',
className: 'subview-b',
events: {
'mouseover':'over',
},
over: function() {
console.log('mouse over on B');
},
});
You might ask: Why not have the same subview? In this example both sub-views are li, but not in the actual implementation.
So how can I trigger the mouseover event on subview B when hovering over subview A, and vice-versa?
Communicate with Backbone's event model. Trust me when I say this, but when using Backbone, try opt for a modular approach. In other words each view, even though might be related, should not depend on another view. It not only makes testing extremely difficult, it also makes it a nightmare to debug and leads to spaghetti code. Take a look at this article. It explains how views communicate with each-other using the Backbone's event model. You should be familiar with the PubSub pattern. This question is also related.
UPDATE! So If I take your example
var vent = _.extend({}, Backbone.Events);
subViewA = Backbone.View.extend({
tagName: 'li',
className: 'subview-a',
initialize: function () {
vent.on('my-event', this.over, this);
},
events: {
'mouseover':'over',
},
over: function(dataRecieved) {
console.log('mouse over on A');
}
});
subViewB = Backbone.View.extend({
tagName: 'li',
className: 'subview-b',
events: {
'mouseover':'over',
},
over: function() {
vent.trigger('my-event', "data you would like to pass");
},
});