I am a backbone newbie and I am trying to develop a Todo like App.
I have a Main view which is a list view and it has subviews. - the subview content can be edited on double click and it would get saved when the enter key is pressed. - very similar to todo example given in backbone github code.
var SubView = Backbone.View.extend({
tagName: "li",
events: {
"dblclick" : "show_edit_view",
"blur .element" : "close_edit_view",
"keypress .element" : "save_edit_view",
"click button.remove" : "remove_question"
},
initialize: function(){
this.render();
this.listenTo(this.model, "change", this.render);
},
render: function(){
this.$el.html(_.template($("#sub_view_template").html(),this.model.toJSON()));
return this;
},
show_edit_view: function() {
this.$el.find("div.view").addClass("no_show");
this.$el.find("input").removeClass("no_show");
},
close_edit_view: function(){
this.$el.find("div.view").removeClass("no_show");
this.$el.find("input").addClass("no_show");
},
save_edit_view: function(e){
if (e.keyCode == 13) {
this.model.save({name: e.currentTarget.value});
this.close_edit_view();
}
}
});
And the template for this is
<script id="sub_view_template" type="text/x-template">
<div class="view"><%= name %></div>
<input class="element no_show" value="<%= name %>" type="text" /> <button class="remove">Remove</button>
</script>
This one works fine, the model is updated in the view and the update post request is sent to the server.
But, when I change the initialization and save_edit_view functions, only the first change event is fired and not the change events.
initialize: function(){
this.render();
this.listenTo(this.model, "change", this.render);
this.input = this.$("input.element");
},
save_edit_view: function(e){
if (e.keyCode == 13) {
this.model.save({name: $(this.input).val()});
this.close_edit_view();
}
}
I was wondering what could the problem be?
Thanks for any help!!!
The problem is you are referring to only one object. This means when you make the assignment:
this.input = this.$('input.element'); // match the current elements.
You are only getting the value from that exact object. After the first change, this.input is not the same object that contains your new value, and fails to save the model with a new value.
A demonstration that may help:
console.log(this.$('input.element') != this.$('input.element')); // true
This is why the following would work:
save_edit_view: function(e){
if (e.keyCode == 13) {
this.model.save({name: this.$('input.element').val()});
this.close_edit_view();
}
}
I guess this.$("input.element"); refers to the first item from the list.
And when you first time change model value with the value from the first item it works. But second time it doesn't works because the value of the first item still the same.
That is why you have to get input value from the event - e.currentTarget.value
Related
This question already has an answer here:
Backbone click event not firing in template View
(1 answer)
Closed 6 years ago.
I am getting the entire html code using jquery get() method and setting it on the el of backbone view.The view gets rendered perfectly but the click events i added are not firing. As i am a newbie to backbone i am not able to find the issue. Any help would be appreciated.
The currentTabID is contain the div id on which i want this html to be rendered.
view.js
var MyFirstView = Backbone.View.extend({
currentTabID:'',
initialize:function(){
this.render();
},
render: function (){
var self = this;
self.el = self.options.currentTabID;
$.get('resources/html/myBB.html', function(data) {
$(self.el).html(_.template(data));
});
return this;
},
events: {
'click .savebtnBB': 'invokeME'
},
invokeME: function (){
console.log('Fired');
}
});
Html looks something like below
myBB.html
<div id="sample_tab">
<div class="sub-main">
<form>
..
</form>
</div>
<div class="button">
<button class="savebtnBB">click me</button>
</div>
</div>
view.el is an actual dom element holding the event listeners for your view. You're replacing view's reference to that element with some number and appending the template to some other element.
Your view should act like an isolated unit as much as possible. Your code for appending it to something else should be outside the view, where you're creating it. Your code should look something like the following:
var MyFirstView = Backbone.View.extend({
initialize: function() {
var self = this;
$.get('resources/html/myBB.html', function(html) {
self.template = _.template(html);
this.render();
});
},
events: {
'click .savebtnBB': 'invokeME'
},
render: function() {
this.$el.html(this.template({ /*some data for template*/ }));
},
invokeME: function() {
console.log('Fired');
}
});
var viewInstance = new MyFirstView();
/*append to whatever you want*/
$(currentTabID).append(viewInstance.el);
I'm trying to use the backbone.stickit library to bind my form input to the model but can't seem to get the model to update correctly.
The keyup event appears to work correctly, i can see the value change if i use the "onSet" callback to display it:
bindings: {
'#firstName': {
observe: 'firstName',
onSet: function(val, options) {
$('#output').html(val);
}
}
}
Here is my code (Run it on jsfiddle):
HTML
<div id="view">
<form name="form" id="form">
<input id="firstName" type="text"/>
<input type="submit" id="submit"/>
</form>
<div id="output"></div>
</div>
JavaScript
var app = {
Model: Backbone.Model.extend({
firstName: 'test'
}),
View: Backbone.View.extend({
el: "#view",
initialize: function(){
this.model = new app.Model();
this.render();
},
bindings: {
'#firstName': 'firstName'
},
render: function(){
this.$el.html( this.template );
this.stickit();
},
events: {
"submit #form": "submitForm"
},
submitForm: function(e) {
e.preventDefault();
$('#output').html('output:'+this.model.firstName);
}
})
};
var view = new app.View();
The way of getting a model attribute is usally not by accessing the attribute name as an object property, the way you did it this.model.firstName. personally I know a very few cases of such implemntation. The so called right way to do that is by using get method:
this.model.get("firstName").
This will return the current model value.
I usually define getters and setters for each model I use, so I would do the following:
getFirstName: function(){
return this.get("firstName");
}
Just looks better and more "easy on the eyes" :) (but totally not a must)
Here's an update of your fiddle: http://jsfiddle.net/srhfvs8h/1/
I'm trying to setup a little app in backbone where I can add items to a list and, when I click them, they'll be deleted. I've managed to add items to the list but when using model.destroy() nothing happens.
When I console.log the click event on the list models I get:
child {cid: "c0", attributes: Object, _changing: false, _previousAttributes: Object, changed: Object…}
for any item I click.
Code is below:
Html:
<h1>INDEX!</h1>
<form class="add-form">
<input type="text" name="name"/>
<hr />
<button type="submit" class="btn">Submit</button>
</form>
<h2>LIST STUFF</h2>
<ul class="blah">
{{#each indexCollection}}
<li class="li-class">{{name}}</li>
{{/each}}
</ul>
Javascript:
//Local Storage
App.Storage.Local = new Backbone.LocalStorage('localIndexList1-backbone');
//Index Model
App.Models.IndexModel = Backbone.Model.extend({
localStorage: App.Storage.Local,
defualts:{
name:''
},
urlRoot: '/'
});
//Index Collection
App.Collections.IndexCollection = Backbone.Collection.extend({
localStorage: App.Storage.Local,
model: App.Models.IndexModel,
initialize: function(){
console.log('Collection initialised');
},
url: '/'
});
//View for H1 and input form
App.Views.IndexView = Backbone.View.extend({
el: '.page',
events:{
'submit .add-form' : 'addNew',
'click' : 'deleteMe'
},
initialize: function(){
console.log('IndexView initialised');
},
addNew: function(ev){
// ev.preventDefault();
var submitEntry = $(ev.currentTarget).serializeObject();
var newEntry = new App.Models.IndexModel();
newEntry.save(submitEntry, {
success: function(newEntry){
// router.navigate('', {trigger: true});
console.log('SUCESSS!!!!!!!!!');
}
});
},
deleteMe: function(){
console.log(this.model);
//Whatever I put here will not work
}
});
//View for list
App.Views.ListView = Backbone.View.extend({
el: '.page',
initialize: function(){
console.log('ListView initialised');
},
template: Handlebars.compile($('#list').html()),
render: function(){
this.$el.html(this.template);
var that = this;
var indexCollection = new App.Collections.IndexCollection();
indexCollection.fetch({
success:function(indexCollection){
that.$el.html(that.template({indexCollection: indexCollection.toJSON()}));
}
});
}
});
Would anyone be able to help letting me know where I am going wrong?
Thanks!
Where are you creating one IndexView for each of your collection models? You should have an item view, configure its model to be one IndexModel, and move your delete code to that particular view. When you do that, you should also call remove in this item view.
This is why something like Backbone.Marionette helps a lot. Just throw in a CollectionView and you're done.
Think of it like this:
"list view" -> has a collection
"item view" -> has a single model
Anything you need to on the collection level (like adding a new one, re-loading, whatever), do it on your list view. Anything you need on model level (editing, saving, deleting), do it on your item view.
I have seen examples of people calling this.render() when listening to changes to the model.
initialize : function() {
this.listenTo(this.model, "change:someAttribute", this.render());
this.listenTo(this.model, "change:someOtherAttribute", this.render());
}
if the render() function is creating a view from some underscore template and attaching it to the html document, what happens to the existing HTML that was attached originally? If I had a drop down selected and some text in one of the view's fields, why aren't they reset to the default values when the render() function is called?
What happens to your DOM depends on what you do in your render method, if you replace a whole elements html with
this.$el.html( _.template( htmlString, data ) );
of if you just append to it
this.$el.append( _.template( htmlString, data ) );
There are different scenarios based on how you set up your app, but i think you will mostly have the render method replacing the whole element, and then you will have an event listener bound to collection changes for example, so if a model is added to the collection you append a sub view to the main views el.
var view1 = Backbone.View.extend({
el: '#view1div',
initialize: function(){
this.collection.bind('reset', this.render, this);
this.collection.bind('add', this.addElement, this);
},
render: function(){
this.$el.html( '<ul id="list"></ul>' );
},
addElement: function(){
$('#list').append( '<li>something</li>' );
}
});
This is my code:
$(function (){
var Slide = Backbone.Model.extend({
defaults: {
castid :1,
id :1
},
urlRoot: function(){
return 'slidecasts/' + this.get("castid") + '/slides/';
},
});
var SlideView = Backbone.View.extend({
el: $("#presentation"),
events: {
'click #next': 'next',
'click #previous': 'previous',
},
initialize: function(){
_.bindAll(this, 'render', 'next');
this.model.bind('change', this.render);
this.render();
},
render: function(){
this.model.fetch();
var variables = {
presentation_name: "This is a Slide-Number: ",
slidenumber: "xxx",
imageurl: this.model.url() +"/"+ this.model.get('imageLinks'),
slide_content: this.model.get("content")};
var template = _.template( $("#slide_template").html(), variables );
this.$el.html( template );
return this;
},
next: function(){
console.log(this.model.id);
this.model.nextslide();
},
previous: function(){
console.log("previous function in view");
}
});
testslide = new Slide();
var slideView = new SlideView({model: testslide});
});
This works fine but in the debug console I always see a GET Request to "slidecasts/1/slides/1/undefined" which of course fails. I don't really understand where I trigger this get request.
Edit - the template code
<script type="text/template" id="slide_template">
<label>Presentation <%= presentation_name %> </label> <br/>
<img src="<%= imageurl %>" id="slide_pic" /> <br/>
<textarea id="slide_content">
<%= slide_content %>
</textarea>
<div id="next">next slide </div>
<div id="previous">previous slide </div>
</script>
You have an asynchronous problem.
This is the sequence of events:
You call this.model.fetch() to populate the model.
You say variables.imageurl = this.model.url() + '/' + this.model.get('imageLinks').
The (asynchronous) fetch hasn't returned yet so this.model.get('imageLinks') is undefined.
You build the HTML and use this.$el.html(template) to update the page.
The browser renders your HTML using the incorrect imageurl from 2.
A bad GET request is logged because of 5.
The fetch from 1 returns from the server and triggers a 'change' event.
The 'change' event triggers a new call to render.
This render call has a fully populated this.model so variables.imageurl is correct and the HTML comes out right this time.
If you let the fetch trigger the render then the problem will go away:
initialize: function(){
_.bindAll(this, 'render', 'next');
this.model.bind('change', this.render);
this.model.fetch();
},
render: function() {
// As before except no this.model.fetch()
}
How I can't see the template you are using I'm just guessing here:
The problem is in this line:
this.model.url() +"/"+ this.model.get('imageLinks'),
Your template is trying to define an <img> element with such URL but the imageLinks attribute is undefined.