I am learning the backbone currently, and facing the following problem.
I want to append new elements to the this.el element of the View. But it doesn't happen.
here is my code:
var MyData = Backbone.Model.extend({
initialize: function() {
this.bind('error', function(model, error) {
console.log('error:' + error);
});
},
defaults: {
name: "Jo",
age: 18,
skill: 0
},
validate: function(attributes) {
if (attributes.name == "Jonh")
return false;
return true;
}
});
var MyView1 = Backbone.View.extend({
initialize: function() {
this.$el.empty();
},
el: '#middle',
events: {
"click": "render"
},
render: function() {
this.$el.append(this.model.get('name'));
}
});
var myData = new MyData();
var myView1 = new MyView1( {model: myData} );
int the html file there is a div element defined as follows:
<div id="middle"></div>
The code works without error, but I dont see any appended elements.
I have also tried to append like this:
this.$el.append($("<p>").append(this.model.get('name')));
Since your javascript script loads before the page is loaded, Backbone View can't see #middle div, so you have to initialize views after page is loaded:
$(document).ready(function() {
// load your views
});
I just loaded this up in my IDE and it worked fine. You console didn't show you any errors?
Related
I am creating a crud web app with backbone. I am writing the functionality to update a resource (PUT). I am trying to achieve this by fetching a models properties from the server (see the SubscriberView) and on successfully fetching the resource to instantiate a SubscriberEditView whereby the newly fetched model is passed.
So far this works as expected; SubscriberEditView renders an html form which is populated with the model instance properties.
When I enter a new login value into the form I can trigger the update function which successfully makes a PUT request to the server resource and updates the model instance as expected.
However, the problem is that when I then repeat this process with another model instance the PUT request is made against the curent model AND the previously instantiated model.
Is the reason for this because I now have two instances of SubscriberEditView? Or is it something else that I have missed/misunderstood.
Please see below the described code.
// The view for a single subscriber
var SubscriberView = Backbone.View.extend({
tagName: 'tr',
template: _.template($('#subscribers-tmpl').html()),
initialize: function() {
this.listenTo(this.model, 'destroy', this.remove);
},
render: function() {
var html = this.template(this.model.toJSON());
this.$el.html(html);
return this;
},
events: {
'click .remove': 'onRemove',
'click .edit-subscriber': 'editSubscriber',
},
editSubscriber: function() {
var getSubscriberModel = this.model.set('id', this.model.attributes.id, {silent:true})
getSubscriberModel.fetch({
success: function (model, response) {
$('#addSubscriber').fadeOut();
new SubscriberEditView({model:model});
},
error: function (response) {
console.log('There was an error');
}
});
},
onRemove: function() {
this.model.destroy();
}
});
// The edit view
var SubscriberEditView = Backbone.View.extend({
tagName: 'div',
el: '#updateSubscriber',
template: _.template($('#subscriberEdit-tmpl').html()),
initialize: function() {
this.model.on('sync', this.render, this);
},
events: {
'click #close': 'cancel',
'click .save-subscriber': 'update'
},
update: function() {
var $login = this.$('#login');
this.model.save({
login: $login.val(),
},
{
dataType: 'text',
success: function (model, response, options) {
console.log('success');
},
error: function (model, response, options) {
console.log('error');
}
});
},
cancel: function() {
$('#addSubscriber').fadeIn();
$('#editInner').fadeOut();
},
render: function() {
var html = this.template(this.model.toJSON());
this.$el.html(html);
},
});
If anyone could help then that would be greatly appreciated.
Cheers
The issue is el: '#updateSubscriber',. All your view instances are pointing to same element to which events are delegated. So clicking on any of the .save-subscriber will trigger update for all the view instances. You should not specify el for a view that is going to have more than one instance.
I am trying to write a simple example using Backbone.js for study. Some how nothing gets printed in the browser. Need a little help here. The code is given below.
Html:
<div id="container">
<ul id="person-list">
</ul>
</div>
Models
var Person = Backbone.Model.extend({
defaults: {
id: 0,
name: ''
}
});
var PersonStore = Backbone.Collection.extend({
model: Person,
url: 'api/person', //currently not using
initialize: function () {
console.log("Store initialize");
}
});
Views
var PersonView = Backbone.View.extend({
tagName: 'li',
initialize: function () {
_.bindAll(this, "render");
},
render: function () {
$(this.el).append(this.model.name) //model.name shows undefined here
return this;
}
});
var PersonListView = Backbone.View.extend({
el: $('#person-list'),
tagName:'ul',
initialize: function () {
_.bindAll(this, "render");
this.render();
},
render: function () {
self = this;
this.collection.each(function (person) { //name property undefined here on person
var personView = new PersonView({ model: person });
$(self.el).append(personView.render().el);
});
}
});
Sample Run
var persons = new PersonStore([
new Person({id:1, name: "Person 1"}),
new Person({ id: 2, name: "Person 2" }),
]);
new PersonListView({ collection: persons });
The above setup prints nothing(blank) on screen. I have struggled now for some time and need a little help here as to why the two Person's name does not get displayed in the browser.
To make your code work you have to replace
this.$el.append(this.model.name)
with
this.$el.append(this.model.get('name'))
Always use method .get() to access model properties.
Also i highly recommend you use templates for rendering views. This approach let you write .render() implementation once and will be no need to change it if you need visual changes, you can make in template
http://jsfiddle.net/3pSg7/
I wonder if someone can help to find what's wrong in this case.
I get "Uncaught ReferenceError: text is not defined" in line 6.
Using template and local .txt files for testing until APIs are available.
Backbone.js model script:
var Letter = Backbone.Model.extend( {
urlRoot: 'data/json/news',
initialize: function() {
},
defaults: {
_type: "",
text: "",
is_read: 0
}
});
var News = Backbone.Collection.extend({
model: Letter,
url: 'data/json/list_news.txt',
initialize: function() {
},
fetchMyNews: function() {
this.fetch({async:false});
}
});
var news = new News();
View script:
var NewsView = Backbone.View.extend({
initialize: function() {
this.isShown = false;
this.render();
this.listenTo(news, "all", this.doListen);
},
doListen: function(eventName){
if(eventName == "change"){
this.render();
}
},
isShown: false,
events: {
},
render: function() {
this.$el.attr("z-index", "1000");
news.fetchMyNews();
var sHtml = JST["news/row"](news.attributes);
$("#news_tbody").html(sHtml);
}
});
a few things in your code.
you are defining a global variable 'news' for your collection. that's not recommend, you can just pass a new collection to your view when you instantiate it :
var NewsView = new NewsView({
collection: new News()
});
and change all your 'news' reference in the view to 'this.collection'
and, I usually don't like async ajax calls. try to change them to callbacks, or just listen to events in your view. oh, and also, try not to fetch data in your render(). your function should only do what they are named for. :)
so in your view:
initialize: function() {
this.isShown = false;
this.listenTo(this.collection, "all", this.doListen);
this.collection.fetch();
},
doListen: function(eventName){
if(eventName == "change" || eventName == 'reset'){
this.render();
}
}
and in your render:
var sHtml = JST["news/row"](new.attributes);
$("#news_tbody").html(sHtml);
you are calling news.attributes, news is a collection here..."attributes" doesn't give you anything. I'm not sure what your template looks like, but you may be calling '.text' in your template, which is giving your this error here since news.attributes is undefined.
So for some reason navigate won't work in one of my views. I'm doing everything in one file for now, so that may be the problem. Also I know the code is horrible, I'm just messing around with backbone right now.
EDIT: I put a console.log() in MarketingPage's function route and it never gets called, so there must be something wrong with the view.
Also, this is the error I'm getting from chrome dev tools:
Error in event handler for 'undefined': IndexSizeError: DOM Exception 1 Error: Index or size was negative, or greater than the allowed value.
at P (chrome-extension://mgijmajocgfcbeboacabfgobmjgjcoja/content_js_min.js:16:142)
at null.<anonymous> (chrome-extension://mgijmajocgfcbeboacabfgobmjgjcoja/content_js_min.js:18:417)
at chrome-extension://mgijmajocgfcbeboacabfgobmjgjcoja/content_js_min.js:1:182
at miscellaneous_bindings:288:9
at chrome.Event.dispatchToListener (event_bindings:390:21)
at chrome.Event.dispatch_ (event_bindings:376:27)
at chrome.Event.dispatch (event_bindings:396:17)
at Object.chromeHidden.Port.dispatchOnMessage (miscellaneous_bindings:254:22)
Here's my code:
/*global public, $*/
window.public = {
Models: {},
Collections: {},
Views: {},
Routers: {
},
init: function () {
console.log('Hello from Backbone!');
}
};
var App = Backbone.Router.extend({
routes: {
'': 'index',
'register': 'route_register',
},
index: function(){
var marketing_page = new MarketingPage();
},
route_register: function(){
var register_view = new RegisterView();
}
});
window.app = new App();
var User = Backbone.Model.extend({
url: '/user',
defaults: {
email: '',
password: ''
}
});
var MarketingPage = Backbone.View.extend({
initialize: function(){
this.render();
},
render: function(){
var template = _.template($("#marketing-page").html());
$('.search-box').after(template);
},
events: {
'dblclick': 'route'
},
route: function(e){
e.preventDefault();
console.log("In route");
window.app.navigate('register', {trigger: true});
this.remove();
}
});
var RegisterView = Backbone.View.extend({
initialize: function() {
this.render();
},
render: function(){
var template = _.template($("#register-template").html());
$('.search-box').after(template);
}
});
$(document).ready(function () {
Backbone.history.start();
});
When I type host/#register into the browser directly, the register view gets rendered, but no matter what I do the click event won't seem to work...
Since the handler function route isn't being called, it's likely that the event delegation isn't working.
One thing to note is that the event handling that is set up in a Backbone View is scoped to only that view's el. I don't see where yours is set up explicitly, so it might be creating an empty div, then handling events inside that empty div (which you don't want).
One trick I use for quick prototypes is to set the view's el with a jQuery selector pointing to something that exists on the page already, then in the render, show it with a .show().
Since you're not really doing that, here's one thing you could try. What we're doing is setting the $el content and then calling delegateEvents to make sure that the events and handlers are being bound.
var MarketingPage = Backbone.View.extend({
initialize: function(){
this.render();
},
render: function(){
this.$el.html(_.template($("#marketing-page").html()));
$('.search-box').after(this.$el);
this.delegateEvents();
},
events: {
'dblclick': 'route'
},
route: function(e){
e.preventDefault();
console.log("In route");
window.app.navigate('register', {trigger: true});
this.remove();
}
});
Backbone.js views delegateEvents do not get bound (sometimes)
http://backbonejs.org/#View-delegateEvents
I've looked around for a while now and have not been able to find anything that suggests what the cause of this is.
My code:
var faqView = Backbone.View.extend({
tagName: 'div',
id: 'faq-list',
initialize: function() {
var view = this;
this.collection = new faqCollection();
this.collection.fetch({
success: function(collection, response) {
collection.each(function(faq){
view.$el.append(_.template($('script#faq_item').html(),{faq: faq.attributes}));
});
},
error: function(collection, response) {
view.$el.html("<p>Unable to get the FAQ items.<br>Please try again later.</p>");
}
});
},
render: function() {
this.$el.appendTo('div#container');
return this;
},
events: {
'click h3': 'toggleAnswer'
},
toggleAnswer: function(event) {
console.log(this);
console.log(event);
}
});
var router = Backbone.Router.extend({
routes: {
"faq": "faq",
"*other": "defaultRoute"
},
faqView: {},
initialize: function() {
this.faqView = new faqView();
},
defaultRoute: function() {
this.resetPage();
},
faq: function() {
this.resetPage();
$('body').addClass('page-faq');
this.faqView.render();
},
resetPage: function() {
$('body').removeClass('page-faq');
this.faqView.remove();
}
});
The above code is included as the last items in the <body>. The HTML is as follows.
<body>
<div id="container">
</div>
<script type="text/template" id="faq_item">
<h3 class="contracted"><span>{{faq.question}}</span></h3>
<p style="display: none;">{{faq.answer}}</p>
</script>
<script type="text/javascript" src="./js/models.js"></script>
<script type="text/javascript" src="./js/views.js"></script>
<script type="text/javascript" src="./js/collection.js"></script>
<script type="text/javascript" src="./js/router.js"></script>
<script type="text/javascript">
//<![CDATA[
$(function() {
var app = new router;
Backbone.history.start();
});
//]]>
</script>
</body>
All the required elements exist (as far as I can tell) and I'm not manually setting the el attribute of the View. I'm lost as to why the events are not binding/firing when the <h3> is clicked.
Edit No errors thrown and the functionality works if I don't use the router and create the view by it self. e.g.
var app = new faqView();
app.render();
Your problem is in your router. Right here in fact:
resetPage: function() {
$('body').removeClass('page-faq');
this.faqView.remove();
}
View#remove is just jQuery's remove on the view's el by default and that:
[...] method takes elements out of the DOM. [...] all bound events and jQuery data associated with the elements are removed
So once you this.faqView.remove(), the delegate handler that drives the view's events is gone.
The usual approach is to create and destroy views as needed instead of creating a view and caching it for later. Your router should look more like this:
var router = Backbone.Router.extend({
routes: {
"faq": "faq",
"*other": "defaultRoute"
},
defaultRoute: function() {
this.resetPage();
},
faq: function() {
this.resetPage();
$('body').addClass('page-faq');
this.view = new faqView();
this.view.render();
},
resetPage: function() {
$('body').removeClass('page-faq');
if(this.view)
this.view.remove();
}
});
Demo: http://jsfiddle.net/ambiguous/aDtDT/
You could try messing around with detach inside an overridden remove method in faqView as well but there's really no need to have an instance of faqView around all the time: create it when you need it and remove it when you don't.