I'm working on an app, in which there are two panels - left and right panel. The left panel is a list of different items and when any one of it is clicked the right panel displays information about that item.
The right panel has the same view, only the data differs.
How can this be achieved in Backbone. Currently, I'm creating instances of collection for each item and passing it to the view. But all instances are having the same data.
I'm new to Backbone and I've to fix this myself.
If I understand the question correctly, you can do this. There should be 3 Objects, Model,LeftView and RightView. For each of items in left panel, you can instantiate a LeftView and a Model. And once clicked on any of the LeftViews, you can instantiate RightView based on the same model. You can use the same model for RightView as LeftView is also having same data.
From what you're saying, it sounds like you really only want a single collection instance, which will hold the items that you're referring to (model instances in the collection).
There should also be 3 views in this scenario — one for the List (the left panel), one for the individual ListItems in that list and one for displaying information about the Item itself (the right panel).
List
The List view holds a reference to the collection and appends a ListItem view for each item in the collection. You may later want to subscribe to events on the collection to automatically append/remove items from the list when new ones are added/remove from the collection. This is the view where that should be handled.
List = Backbone.View.extend({
el: '#list',
initialize: function(){
_.bindAll(this)
this.collection.each(this.appendItem)
},
appendItem: function(item) {
view = new ListItem({model: item})
$(this.el).append(view.render().el)
}
})
ListItem
The ListItem view holds a reference to a particular model and renders the list item inside the list for that model. It also handles the click event, which renders the Item view for that model.
ListItem = Backbone.View.extend({
tagName: 'li',
events: {
"click" : "showInfo"
},
initialize: function(){
_.bindAll(this)
},
render: function(){
$(this.el).html(renderTemplate("listItem", this.model))
return this
},
showInfo: function(){
view = new Item({model: this.model})
$('#item').html(view.render().el)
}
})
Item
The Item view also holds reference to a particular model and renders information about that model. This is the right panel that you're referring to.
Item = Backbone.View.extend({
initialize: function(){
_.bindAll(this)
},
render: function(){
$(this.el).html(renderTemplate("item", this.model))
return this
}
})
Here is a fiddle to show things in action.
Related
I've got a few list elements with some movie titles in them. When a user clicks on a list element containing a movie title I want to save that movie title into the Backbone collection so that the movie title is added to the users record.
This is my indexView
class Movieseat.Views.MovieseatsIndex extends Backbone.View
template: JST['movieseats/index']
initialize: ->
#collection.on('reset', #render, this)
render: ->
$(#el).html(#template(movies: #collection))
this
events: ->
"click li": "showtext"
showtext: (e) ->
movie_title = $(e.target).text()
#collection.create title: $('movie_title').text()
My Collections
class Movieseat.Collections.Movieseats extends Backbone.Collection
url: '/api/movies'
defaults:
title: ""
And my template
<% for movie in #movies.models: %>
<div class="movie-frame"><%= movie.get('title') %></div>
<% end %>
I had this working using backbone models, but I since then I've been following the RailsCast backbone video's and there he's using collections so I'm switching over.
On my homepage (where I render the template) I have a few .movie-frame elements filled with a movie title from when I was using models.
But when I click on a element now, I do get a new .movie-frame element, but it doesn't have any content. So I'm guessing the problem is within this part
showtext: (e) ->
movie_title = $(e.target).text()
#collection.create title: $('movie_title').text()
I've been looking in this code for to long and feel I'm missing something obvious.
Sometimes you just need to take a step back, take a short walk and get some fresh air.
$('movie_title')
This isn't a variable anymore
$movie_title
This is. Now I've changed this the collection is saving the variable into the Collection.
I have a container .js-todoslist for todo lists. Also I have several views TodosListView for each todolist. All TodosListView's are created from parent view and binded to the same el .js-todoslist.
Todos lists are not rendered on initialize, I need to render different Todos' lists by clicking buttons so each list has showTodolist method that shows it.
The problem is, though at the moment only one list can be rendered, all of them are actually initialized. And when I press .add-btn in rendered List, all lists have add function trigged (not only current rendered).
TodosListView = Backbone.View.extend
events:
"click .add-btn": "add"
initialize: (options) ->
self = this
#model = #options.model
# I've tried to undelegate event after initialize, but no luck
# #$el.undelegate('.add-btn', 'click');
# #undelegateEvents()
# show this todolist
showTodolist: ->
#$el.html #template(#model.toJSON())
render: ->
#
add: ->
console.log "Add to Todos list #" + #model.get("id")
How to avoid this (excpt creating different views for each list)? Thanks.
Your problem is that since you are using the same el for different views, when you render a view it does not unbind the events of the previous view.
Before rendering the view, use:
this.undelegateEvents();
There is a Bckabone view Product:
Product = Backbone.View.extend({
templateBasic: _.template($("#pcard-basic").html()),
templateFull: _.template($("#pcard-full").html()),
initialize: function() {
this.render(this.templateBasic);
},
// ...
Here's my draft: http://jsfiddle.net/challenger/xQkeP/73
How do I hide/show other views when one of them gets chosen/unchosen to view its full template so it could expand to a full container width.
Should I use a view for an entire collection? How do I deal with event handling?
Thanks!
EDIT
That's my final draft: http://jsfiddle.net/challenger/xQkeP/
But still I'm not sure whether I could achieve the same result in more elegant manner? I just think that hiding siblings is not the best way of resolving it:
viewBasic: function(e) {
e.preventDefault();
this.render(this.templateBasic);
if(this.switchedToFull) {
this.$el.siblings().show();
this.switchedToFull = false;
}
},
viewFull: function(e) {
e.preventDefault();
this.render(this.templateFull);
this.$el.siblings().hide();
this.switchedToFull = true;
}
If I understand correctly you want to display all your models inside your collections in two different ways, leaving the choice of how to present them to the user, right?
One way you can do this is to create a main view where the user choice is made. When the user decides you should trigger a method from that view that renders every model from the collection using a different template. On your main view you should have a container (table, div, ul, etc) where you'll append each of the model view.
So, in the end, you have to views. One acting as a container for the collection that takes care of handling the users choice. Then you have another view to render a single model from the collection. This view has to templates that can be used. On the main view you iterate over the collection creating a new view instance for each model to append in the container using a different template depending on the user decision.
I'm working with Backbone.js for the first time, and trying to get my head around how it works. I have a search form that pulls in results via Ajax and writes them out to the page dynamically.
I'm now trying to figure out how best to structure this in Backbone - I read this SO question, but I don't completely understand how to wire the form and the results together.
Here's my HTML:
<form id="flight-options"> <!-- options for user to choose-->
<input type="radio" name="journey" value="single">Single<br/><input type="radio" name="journey" value="return">Return
<input type="checkbox" name="airline" value="aa">AA<br><input type="checkbox" name="airline" value="united">United
</form>
<div id="results"> <!-- results, written by Ajax -->
<h3>Results</h3>
<ul id="results-list">
</div>
Here's how I'm thinking of structuring the Backbone code:
var SearchModel = Backbone.Model.extend({
performSearch: function(str) {
// fire the ajax request. provide a bound
// _searchComplete as the callback
},
_searchComplete: function(results) {
this.set("searchResults", results);
}
});
var SearchFormView = Backbone.View.extend({
tagName: "form",
id: "flight-options",
events: {
"click input": "getResults"
},
getResults: function() {
// Get values of selected options, use them to construct Ajax query.
// Also toggle 'selected' CSS classes on selected inputs here?
this.model.performSearch(options);
}
});
var SearchResultsView = Backbone.View.extend({
tagName: "ul",
id: "results-list",
initialize: function() {
this.model.on("change:searchResults", this.displayResults, this);
},
displayResults: function(model, results) {
//append results to results-list here.
//update contents of blurb here?
}
});
var searchModel = new SearchModel();
var searchFormView = new SearchFormView({ model: searchModel });
var searchResultsView = new SearchResultsView({ model: searchModel });
My questions:
Is this basically a sensible structure to use, with one view for the form and one for the results - the first view updating the model, the second view watching the model?
I also want to update the contents of the <h3> results header when there are new results - where is the most sensible place to do this, in the above code?
I want to toggle the selected class on an input when the user clicks on a form input - where is the logical place to do this, within the above code?
Thanks for your help.
Yes, that's a logical organization and a great way to use Backbone Views.
You could approach this a couple ways:
Have a separate View for the title (e.g. SearchResultsTitleView) that also listens for changes on the model. That seems a bit excessive to me.
Have your SearchResultsView update both the title <h3> and results <ul>. Instead of binding itself to the #results-list <ul>, it might bind to the #results <div> and have two functions, one for updating each child.
That would seem like the responsibility of the SearchFormView, either listening for changes on the model or simply updating the state when the event occurs.
I have a many-to-many relationship with two of my backbone.js models implemented using a pivot table on the serverside. I'm trying to figure out how to structure it clientside. My current structure is:
1) I have a Tag model, and a TagView which renders a checkbox and the tag label, I have a checked event on the checkbox which does nothing at the moment.
I have a TagsCollection, which holds a bunch of Tags.
I have a TagsCollectionView, which binds add, reset etc of the TagsCollection, and adds TagViews for the added Tags, renders them, and appends the html to its current html (on reset, the html is reset).
I have a global TagCollection instance which contains all the possible tags
I have a Notes Model which contains an (empty) TagCollection called selectedtags on init.
The server returns an array of selected tagids for each Notes, which I add to its TagCollection.
Now comes the hard part, tying it all together.. my NotesView has its own TagsCollectionView which is bound to the global TagsCollection (so it can list all the Tags).. now, how do I get a checked event on the checkedbox of its sub TagViews to trigger an add to this Notes model's selectedtags? Should I provide a reference to the this Notes model instance to the TagsCollectionView on init which then provides it to all the TagViews it creates, whose checked event then adds/removes items from that model? That's the best way I can figure out how to do this, any other thoughts would be appreciated.
View is only for visual display of model. Please specify the need for TagsCollectionView in more details:
Use change event for checking the checkbox.
I would advise incremental coding. First work with the Tag and TagView, As it works, continue adding collection to hold the Tags. After that you can add Notes. It's a 'divide and conquer' :)
Don't confuse with the whole design, it is very simple when you start.
I can not provide the whole design due to lack of requirement details. but, I think below code should trigger the starting point of your design.
var TagsCollectionView=Backbone.View.extend({
el:$(),
});
var Tag=Backbone.Model.extend({
defaults:{
// define the default properties
}
});
var TagView=Backbone.View.extend({
el:$("body"),
events:{
'change #checkBox':'customFunction'
},
initialize: function(){
_.bindAll(this, 'render','customFunction');
this.render();
},
render:function(){
var tag=new Tag();
// code to render the checkbox and label
},
customFunction:function(){
// whatever you want to do after checking event of checkbox
}
});
// collection to collect all the tags
var TagCollection=Backbone.Collection.extend({
model:Tag
});
var Notes=Backbone.Model.extend({
defaults:{
'tagCollection':TagCollection
}
});
// you do not require TagsCollectionView