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");
},
});
Related
In my ongoing self thought process by building my simple blog app I am finding solutions to problems and encountering new ones.
Now successfully routing to a second view from a first one, and page is populated by the new views html.
Successfully save to the db new posts from second view, which is a form to add new posts.
First problem is:
In the first view I have the posts rendered five times, in order. There is not any js console messages. I have saved those posts each only one time from the second view, which is my postformview for saving posts.
Second problem is: From second view to the first view when navigated with the browser back button no posts rendered into page only the headers etc in one of the templates of this page is rendered.
What can be the issue here which I miss?
first view:
var postsListView = Backbone.View.extend({
collection: new postsCollection(),//! The Collection may be created to use in view. with new Coolectionname(). SOLVED it must be created, this attr is not suffcent and is not crating it.
template1: _.template( $('#postsListTemplate').html() ),//!!!Once forgot .html().
template2: _.template( $('#postsListItemTemplate').html() ),
initialize: function(){
this.collection.fetch();
this.collection.on('add', this.renderPostsListItem, this);
},
render: function(){
this.$el.html( this.template1() );//!this.el or this.$el. (each) or (each.toJSON()). SOLVED: use this.$el alongside el: a string, without $().
return this;
//* return this in every views render: if you want to chain el to render() of the view, for example in router while pcaing the rendered views el into DOM.
},
renderPostsListItem: function(){
console.log("view method renderPostsListItem have been reached.");
this.ul = 'ul';
this.collection.forEach(function(each){
$(this.ul).append( this.template2( each.attributes ) );
}, this);
return this;
},
events: {
"click a": 'toPostFormRoute'
},
toPostFormRoute: function(e){
console.log("view method toPostFormRoute have been reached.");
e.preventDefault();
Backbone.history.navigate( '/posts/postform' , {trigger: true});
console.log("view method toPostFormRoute have been reached.");
}
});
router:
//Define Client-Side Routes
var appRouter = Backbone.Router.extend({
el: 'body',
routes: {
'posts/postform': 'viewPostForm',
'': 'viewPosts'
},
viewPosts: function(){
console.log("router method viewPosts have been reached.");
this.postslistview = new postsListView();
$(this.el).html( this.postslistview.render().el );
},
viewPostForm: function(){
console.log("router method viewPostForm have been reached.");
this.postformview = new postFormView();
$(this.el).html( this.postformview.render().el );
}
});
UPDATE: Variation. adding each model when an add event fired y passing the model added to the method and rendering template only with it, appending only it. not iterating through collection them all.
This solves first issue but not the second issue. What can be the specific issue for this?
code fragment from the first view:
initialize: function(){
this.collection.fetch();
this.collection.on('add', this.renderPostsListItem, this);
},
renderPostsListItem: function(model){
console.log("view method renderPostsListItem have been reached.");
this.$el.find('ul').append( this.template2(model.toJSON()) );
return this;
},
Issue :
When a new item/model is added to the collection, all the items present in the collection are rendered/appended to the view's EL instead of only the newly added.
Root Cause :
renderPostsListItem#3
Solution
renderPostsListItem: function(model){
console.log("view method renderPostsListItem have been reached.");
this.collection.forEach(function(each){
this.$el.find('ul').append( this.template2(model.toJSON()) );
}, this);
return this;
},
http://backbonejs.org/#Collection-add
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 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"
}
});
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 });
}