I'm trying to develop my first backbone application. All seems ok, but when i render the view and append some html to the $el, nothing is rendered in the page.
Rest service calls done ok, the Backbone.Router.extend is declared inside $(document).ready(function () {}); to ensure that the DOM is created.
Debugging my javascript, the el element get to contain the correct value in the innerHTML property, but when the whole page is rendered, this value doesn't appear in the page.
¿What am i doing wrong?
My View code:
window.ProductsListView = Backbone.View.extend({
id: 'tblProducts',
tagName: 'div',
initialize: function (options) {
this.model.on('reset', this.render, this);
},
render: function () {
// save a reference to the view object
var self = this;
// instantiate and render children
this.model.each(function (item) {
var itemView = new ProductListItemView({ model: item });
var elValue = itemView.render().el;
self.$el.append(elValue); // Here: the $el innerHTML is ok, but in the page it disappear. The id of element is div#tblProducts, so the element seems correct
});
return this;
}
});
window.ProductListItemView = Backbone.View.extend({
tagName: 'div',
template: _.template(
'<%= title %>'
),
initialize: function (options) {
this.model.on('change', this.render, this);
this.model.on('reset', this.render, this);
this.model.on('destroy', this.close, this);
},
render: function () {
$(this.el).html(this.template(this.model.toJSON()));
// $(this.el).html('aaaaaa'); // This neither works: it's not a template problem
return this;
},
close: function () {
$(this.el).unbind();
$(this.el).remove();
}
});
Here i load products (inside Backbone.Router.extend). This is executed correctly:
this.productsList = new ProductsCollection();
this.productsListView = new ProductsListView({ model: this.productsList });
this.productsList.fetch();
And this is the html element i want to render:
<div id="tblProducts">
</div>
Thanks in advance,
From the code you have posted, you are not actually inserting your ProductsListView in to the DOM or attaching it to an existing DOM element.
The way I like to look at it is you have two types of Views:
Those that are dynamically generated based on data returned from the server
Those that already exist on the page
Usually in the case of lists, the list already exists on the page and it's items are dynamically added. I have taken your code and restructured it slightly in this jsfiddle. You will see that the ProductListView is binding to an existing ul, and ProductItemView's are dynamically appended when they are added to the Collection.
Updated jsfiddle to demonstrate Collection.reset
The el property exists within the view if it is rendered or not. You can't say it is ok there because Backbone will create an element if no element is passed (empty div).
If you want to render the view you should determine what is the container of the element? Do you have an html you want to attach the view to?
Try passing a container element by calling the view with an el like
this.productsListView = new ProductsListView({ model: this.productsList, el : $("#container") });
Of course you can create the view and attach it to the DOM later:
el: $("#someElementID") //grab an existing element
el.append(view.render().el);
Your view wont exist in the dom until you attach it somewhere.
Related
I created a view and has the ff codes:
var app = app || {};
app.singleFlowerView = Backbone.View.extend({
tagName: 'article',
className: 'flowerListItem',
// tells where to apply the views
template: _.template( $("#flowerElement").html() ),
// render
render: function(){
var flowerTemplate = this.template(this.model.toJSON());
// el contains all the prop above and pass to backbone
this.$el.html(flowerTemplate);
return this;
},
events: {
'mouseover': 'addBgColor',
'mouseout': 'removeBgColor'
},
addBgColor: function(){
this.$el.addBgColor('bgColorImage');
},
removeBgColor: function(){
this.$el.removeBgColor('bgColorImage');
}
});
When I run this to my HTML file I got the error addBgColor and removeBgColor is not a function. I have the CSS for this and all the models and views were set up.
Am I missing something here? Any idea why events doesn't work?
this.$el.addBgColor is the problem.
The events are triggering but you're calling addBgColor on the $el jQuery object, which is not a jQuery function, like the error message is telling you.
Check what's the difference between $el and el.
Tony, your events are cool and they are running they're just not doing anything.
this.addBgColor() will call your function in a view.
this.$el is referring to the html and there's no property called addBgColor assigned to $el.
You need to do something like change the class on your tag with the functions like so...
addBgColor: function(){
this.$el.className = 'bgColorImage'
},
.bgColorImage {
background-image: url('bgColorImage.jpg');
}
I am trying to place the rendered output of a Collection View onto the dom. However, only the last object in the collection is displayed on the page at the end of the process.
I have a event handler set up on the view so that when an item is clicked, it's title is logged out. Whenever I click on this single element that is placed onto the Dom, the title for each of my objects is logged, even though only one is displayed, so each handler is being applied to the final element but is somehow logging out the correct titles.
Does anybody know how I can render out each item in the collection rather than just the final one? Below is a quick tour through my code.
The end goal is to list out the name of each film.
Model
First, define the model - nothing exciting here
var FilmModel = Backbone.Model.extend({});
View
Here is a simplified version of the View I have made for the Film model
var FilmView = Backbone.View.extend({
// tagName: 'li',
initialize: function() {
this.$el = $('#filmContainer');
},
events: {
'click': 'alertName'
},
alertName: function(){
console.log("User click on "+this.model.get('title'));
},
template: _.template( $('#filmTemplate').html() ),
render: function(){
this.$el.html( this.template( this.model.attributes ) );
return this;
}
});
Collection
Again, seems standard.
var FilmList = Backbone.Collection.extend({
model: FilmModel,
});
Collection View
Adapted from a Codeschool course I took on Backbone
var FilmListView = Backbone.View.extend({
// tagName: 'ul',
render: function(){
this.addAll();
return this;
},
addAll: function(){
this.$el.empty();
this.collection.forEach(this.addOne, this);
},
addOne: function(film){
var filmView = new FilmView( { model: film } );
this.$el.append(filmView.render().el);
// console.log(this.$el);
}
});
Go time
var filmList = new FilmList({});
var filmListView = new FilmListView({
collection: filmList
});
var testFilms = [
{title: "Neverending Story"},
{title: "Toy Story 2"}
];
filmList.reset(testFilms);
filmListView.render();
From my understanding of Backbone so far, what this should be doing is appending, using the template specified in FilmView to render each item in the filmList collection into the el in the filmListView.
However, what actually happens is that the final title is always placed on the DOM.
I initially (when this was pulling in from an API) thought that the issue might be similar to this question, however now that I am resetting with my own testFilms, I can be positive that I am not overriding or setting any id attribute that I shouldn't.
Does anybody have any ideas?
I think it could be that you set the el of FilmView to an id, which should always be unique, however then you loop over the collection and continually reset that el/id with the current model since each FilmView is going to have the same el
This is my first time create a view with Backbone, but I'm not able to render changes to an existing element in the document.
var ParamsView = Backbone.View.extend({
el: $('#node-parameters'),
initialize: function() {
console.log('WOW');
},
render: function() {
console.log('doing it');
console.log(this.$el.length);
console.log($('#node-parameters').length);
this.$el.append('<span>hello world!</span>');
return this;
}
});
var v = new ParamsView();
v->render();
The words hello world! do not appear in the target div.
The console outputs the following when the view is rendered.
WOW
doing it
0
1
So I know that my jQuery selector $('#node-parameters') is finding 1 DOM element, but the view is not use it.
Any ideas what I'm doing wrong?
EDIT: In the JS debugger I can see that this.el is undefined for the view.
Your code is probably equivalent to this :
var ParamsView = Backbone.View.extend({
el: $('#node-parameters'),
initialize: function() {
console.log('WOW');
},
render: function() {
console.log('doing it');
console.log(this.$el.length);
console.log($('#node-parameters').length);
this.$el.append('<span>hello world!</span>');
return this;
}
});
$(document).ready(function() {
var v = new ParamsView();
v.render();
});
http://jsfiddle.net/nikoshr/mNprr/
Notice that you class is declared before the DOM ready event.
You set the el at extend time with $('#node-parameters'). $ is a function that is immediately executed but, and that's why you get an undefined element, #node-parameters does not exist at that point.
By injecting the element with new ParamsView({el: '#node-parameters'}), you set a valid el after the DOM ready event. You could also set it via
var ParamsView = Backbone.View.extend({
el: '#node-parameters'
});
el is then evaluated when you instantiate your class, after the DOM ready event. http://jsfiddle.net/nikoshr/mNprr/1/
I am trying to output some h1 text on the page using backbone view but for some reason it is not working. I can show the h1 if i use it within document ready but not when I use it within the render function.
var HomeView = Backbone.View.extend({
el:'body',
intialize: function () {
this.render();
},
render: function () {
this.$el.empty();
this.$el.append("<h1>My first Backbone app</h1>"); // not showing on the page
return this;
}
})
$(document).ready(function () {
wineApp = new HomeView();
})
this.el is a DOM element while this.$el is a jQuery object. jQuery objects have an append function which is not available for plain DOM elements.
You can also convert the DOM element into a jQuery object by running $(this.el).
It's a typo: the function intialize should be called in i tialize. At the moment the function isn't invoked at all.
Trying to create a todo example app to mess around with backbone. I cannot figure out why the click event for the checkbox of a task is not firing. Here is my code for the TaskCollection, TaskView, and TaskListView:
$(document).ready(function() {
Task = Backbone.Model.extend({});
TaskCollection = Backbone.Collection.extend({
model: 'Task'
});
TaskView = Backbone.View.extend({
tagName: "li",
className: "task",
template: $("#task-template").html(),
initialize: function(options) {
if(options.model) {
this.model = options.model
}
this.model.bind('change',this.render,this);
this.render();
},
events: {
"click .task-complete" : "toggleComplete"
},
render: function(){
model_data = this.model.toJSON();
return $(_.template(this.template, model_data));
},
toggleComplete: function() {
//not calling this function
console.log("toggling task completeness");
}
});
TaskListView = Backbone.View.extend({
el: $("#task-list"),
task_views: [],
initialize: function(options) {
task_collection.bind('add',this.addTask,this);
},
addTask: function(task){
task_li = new TaskView({'model' : task});
this.el.append(task_li.render());
this.task_views.push(task_li);
},
});
});
Template for the task:
<script type='text/template' id='task-template'>
<li class="task">
<input type='checkbox' title='mark complete' class='task-check' />
<span class='task-name'><%= name %></span>
</li>
</script>
I can't seem to figure out why the toggleComplete event will not fire for the tasks. how can I fix this?
The problem here is that the backbone events only set to the element of the view (this.el) when you create a new view. But in your case the element isn't used. So you have the tagName:li attribute in your view, which let backbone create a new li element, but you doesn't use it. All you return is a new list element created from your template but not the element backbone is creating, which you can access by this.el
So you have to add your events manually to your element created by your template using jQuery or add your template as innerHtml to your element:
(this.el.html($(_.template(this.template, model_data)))
Try changing the lines where you set your listeners using .bind() to use .live(). The important difference is .live() should be used when you want to bind listeners to elements that will be created after page load.
The newest version of jQuery does away with this bit of ugliness and simplifies the methods used to set event listeners.
Your event is binding to a class of .task-complete but the class on your checkbox is .task-check
Try modifying your render function to call delegateEvents() like so:
render: function(){
model_data = this.model.toJSON();
this.el = $(_.template(this.template, model_data));
this.delegateEvents();
return this.el;
},
You'd really be better off changing your template to not include the li and then return this.el instead of replacing it, but if you want the events to work you need to have this.el be the root element one way or another; delegateEvents() re-attaches the event stuff, so when you change this.el that should fix the issue.
#Andreas Köberle answers it correctly. You need to assign something to this.elto make events work.
I changed your template and your TaskView#render() function.
This JSFiddle has the changes applied.
New render function:
render: function(){
var model_data = this.model.toJSON();
var rendered_data = _.template(this.template, model_data);
$(this.el).html(rendered_data);
return this;
}
It is recommended that the render() returns this.
One line in your TaskListView#addTask function changes from this.el.append(task_li.render()); to this.el.append(task_li.render().el);.
Template change
Since we are using this.el in the render() function, we have to remove the <li> tag from the template.
<script type='text/template' id='task-template'>
<input type='checkbox' title='mark complete' class='task-complete' />
<span class='task-name'><%= name %></span>
</script>