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.
Related
I have a Rails app in which I have a form that looks something like this:
[ Parent list item 1 ]
[ Parent list item 2 ]
[ Parent list item 3 - expanded ]
[ Child list item 1 ]
Child inline input Child submit button
------------------
[Parent input]
Parent submit button
The parent entity input always works. It is a remote form, using remote: true. When I add a parent object it gets automatically added to the list with the other parent objects. Each parent can have many children, they are displayed and listed when the user expands the corresponding parent list item (like the example above). Users can add more children by entering a value in the Child inline input, this form is also using remote: true.
The problem I'm having is that add children element doesn't always work on the first page load. It works however if I refresh the page. I'm having a hard time to understand why this is.
When I create a parent object the following js.erb is rendered:
# screen_table_id is the list with parent objects.
# localized_strings/localized_string is the tr with the object
$("#screen_table_<%= #localized_string.screen.id %>").append("<%= j render partial: 'localized_strings/localized_string', locals: { screen: #localized_string.screen, localized_string: #localized_string } %>");
# I use the best in place gem to manage inline editing
jQuery('.best_in_place').best_in_place()
The relevant parts of localized_strings/localized_string looks like this:
%tbody{ id: "localized_string_parent_#{localized_string.id}"}
%tr
%td.expandable-column
Clicking this reveals the child objects
/ The list of children is initially hidden
%tbody.hidden[localized_string]
- if localized_string.translations.any?
/ Renders the children
%tr
/ This form doesn't work on page load, after I have added the parent
= render "translations/inline_form", app: localized_string.screen.app, screen: localized_string.screen, localized_string: localized_string, translation: localized_string.translations.build
And translations/inline_form looks like this:
= form_for [app, screen, localized_string, translation], remote: true do |f|
%td{ colspan: 2 }
.inline-form-group
= f.text_field :value, class: "form-control inline-form-control", placeholder: "Translation value", id: "localized_string_input_# {localized_string.id}"
%td
/ Sometimes nothing happens when I click Submit.
= f.submit 'Add translation', class: "btn ban-success-outline"
The faulty flow looks like this:
Page load and I create a parent object (LocalizedString)
It gets added to the list correctly.
Expanding the new parent list element works as expected.
When clicking on the submit button for the child (Translation) does nothing.
Hope my question is understandable. Please comment if you have any comments or need clarification. I'm tankful for all ideas.
Ryan Bates did a great Railscast on this topic Nested Model Form Part 2. There are many interacting dependencies depending on your routes and model associations, but this RailsCast looks to be directly applicable.
I'm pretty sure that my issue was caused by invalid HTML. I previously rendered the form inside the tr tag, like this:
%tr
= render "translations/inline_form", app: localized_string.screen.app, screen: localized_string.screen, localized_string: localized_string, translation: localized_string.translations.build
And the inline_form started with the form itself.
Instead of doing like that I instead tried to wrap it inside a td tag, like this:
# inline_form.html.haml
%td{ colspan: 4 }
# the form wasn't previously inside a td tag.
= form_for [app, screen, localized_string, translation], remote: true, style: "margin-bottom: 0" do |f|
I haven't seen this problem again after this. But I'm not 100% sure that it is the solution, since the problem showed up somewhat randomly.
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();
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.
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 page with two div classes which is fine. One is the collection the other the item.
But when i want to select edit, i need to remove the item view and replace it with the edit link, this is not happening and its staying there, below is my edit class would be great.
Supernote.Views.Notes ||= {}
class Supernote.Views.Notes.EditView extends Backbone.View
template : JST["backbone/templates/notes/edit"]
events :
"submit #edit-note" : "update"
update : (e) ->
e.preventDefault()
e.stopPropagation()
#model.save(null,
success : (note) =>
#model = note
window.location.hash = "/#{#model.id}"
)
render : ->
$(#el).html(#template(#model.toJSON() ))
this.$("form").backboneLink(#model)
return this
Can you post some more of the code? When you say 'edit link', do you mean something in the DOM, or a url in the address bar.
When you call #model.save, Backbone will automatically update the attributes of the model with attributes returned by the server, so the
#model = note
is not required. But if you are not binding the 'change' event on the model to anything, then the view will not update if the model changes.
Are you using Backbone routers to handle changes in the location.hash?
You can remove a view from the DOM by calling #remove()
You could also call $(#el).replace(...) if you wanted to replace it with something.