Backbone.js model not passing to view - javascript

I'm starting out with backbone.js building my first project with backbone-boilerplate.
I have a module named Navitem with a view called Sidebar:
Navitem.Views.Sidebar = Navitem.Views.Layout.extend({
template: "navitem/sidebar",
tagName: 'ul',
beforeRender: function()
{
var me = this;
this.options.navitems.each(function(navitem)
{
//insertView from Layout datatype
me.$el.append(new Navitem.Views.Item({
model: navitem //select the 'ul' in sidebar view and append an Item with model navitem
}).render().el);
});
return this;
}
});
When the sidebar is constructed, a collection containing many Navitem.Model's are passed into it. After debugging, model:navitem seems to be working correctly and passing in the right navitem model to the new Navitem.Views.Item({...}). That class looks like:
Navitem.Views.Item = Navitem.Views.Layout.extend({
tagName: 'li',
template: 'navitem/default'
events: {
click: "navRoute"
},
navRoute : function()
{
app.router.go(this.model.get('target'));
return this;
}
});
The template looks like <%= model.get('label') %>.
For some reason when I call Item.render() in the first code block, it whines that model is undefined in the view. I can't seem to figure out why this is happening. Any thoughts?

Might be related to what was answered here : Backbone.js: Template variable in element attribute doesn't work
You need to pass the model as a plain JSON to your template (unless maybe you're using another version?)
Hope this helps!

I'm doing something similar in a program that I wrote using Backbone Boilerplate and Backbone LayoutManager.
Try adding a serialize function to your Navitem.Views.Item view:
// provide data to the template
serialize: function() {
return this.model.toJSON();
}
and then in the beforeRender function of Navitem.Views.Sidebar:
beforeRender: function(){
_.each(this.options.navitems.models, function(model){
var view = new Navitem.Views.Item({model: model});
this.insertView(view);
}, this);
}
and the navitem/default template could look like this:
<%= label %>
This is untested code (using your views and collections) but doing this has been working for me.

Related

Events Wont work on Backbone.js

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');
}

backbone paginator multi model in a view

I have a shopping cart app made with Backbone.Paginator.Fluenced and forked with this example; https://github.com/msurguy/laravel-backbone-pagination
I made some small changes;
when you click over an item link, it opens a bootstrap modal window.
The code is below.
app.views.ItemView = Backbone.View.extend({
tagName: 'div',
className: 'col-sm-4 col-lg-4 col-md-4',
template: _.template($('#ProductItemTemplate').html()),
events: {
'click a.openModal': 'openModal'
},
initialize: function() {
this.model.bind('change', this.render, this);
this.model.bind('remove', this.remove, this);
},
render : function () {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
openModal : function () {
var view = new app.views.ModalView({model:this.model});
view.render();
}
});
and this is my ModalView to show product details in a modal window.
app.views.ModalView = Backbone.View.extend({
template: _.template($('#modal-bsbb').html()),
initialize: function() {
_.bind(this.render, this);
},
render: function () {
$('#myModalPop').modal({backdrop: 'static',keyboard: true});
$('#myModalPop').html(this.template({
'model':this.model.toJSON()
}));
return this;
}
});
Everything is fine for above codes.
I decided to optimize this code and wanted some improvements on this.
Firstly I am fetching all product data and send these data to modal windows.
I think i must send only main meta data and must fetch details from these window.
So i made a new Backbone Model and Collection;
app.models.ItemDetails = Backbone.Model.extend({});
app.collections.ItemDetails = Backbone.Collection.extend({
model: app.models.ItemDetails,
dataType: 'json',
url : "/api/item-details",
parse: function(response){
return response.data;
}
});
My api returns JSON :
{"data":{"id":8,"title":"Product 8","seo":"product-8","code":"p8","review":"Lorem30"}}
My problem is adding multiple models to ModalView;
I tried a lot of example and questions in blogs&forums couldnt find any solve.
I tried a lot of things ($.extend, to set model and model vs..)
to change ModalView and below codes are last position of them;
app.views.ModalView = Backbone.View.extend({
template: _.template($('#modal-bsbb').html()),
initialize: function() {
_.bind(this.render, this);
},
render: function () {
var itemDetails = new app.collections.ItemDetails(); // this is new line
var model2 = itemDetails.fetch(); // this is new line
$('#myModalPop').modal({backdrop: 'static',keyboard: true});
$('#myModalPop').html(this.template({
'model1':this.model.toJSON(),
'model2':model2.model // this is new line
}));
return this;
}
});
I want to add a second model to my underscore template. But cant!
Firstly when i run below codes on chrome developer console it gets an Object;
but couldnt convert as a new model or JSON.
var itemDetails = new app.collections.ItemDetails();
var model2 = itemDetails.fetch();
console.log(model2); // gets fetch data as an object
I am afraid I am confused about where the problem exactly is.
Sorry guys I am not a backbone expert and probably I am doing something wrong though I searched a lot about it on the forum. I read about it again and again but I could not solve the problem. Could you please help me. Thank you in advance.
SOLVE:
After searchs and by the help of below reply.
I solved my problem.
app.views.ModalView = Backbone.View.extend({
template: _.template($('#modal-bsbb').html()),
initialize: function() {
_.bind(this.render, this);
},
render: function () {
var _thisView = this;
var itemsDetails = new app.collections.ItemsDetails();
itemsDetails.fetch({
success:function(data){
$('#myModalPop').modal({backdrop: 'static',keyboard: true})
.html(_thisView.template({
'model1':_thisView.model.toJSON(),
'model2':data.at(0).toJSON()
}));
}});
}
});
Every request to server using backbone is async, it means that you will not have the returned data immediately after the request, maybe the server still processing the data.
To solve this problem you have 2 ways.
First Way: Callbacks
Inside your Model/Collection
GetSomeData:->
#fetch(
success:=(data)>
console.log data // the returned data from server will be avaiable.
)
Second way: Listen for an trigger.
This one it's more elegant using backbone because you don't write callbacks.
Inside Model
GetSomeData:->
#fecth()
Inside View
initialize:->
#model = new KindOfModel()
#model.on "sync", #render, #
backbone automatically will trigger some events for you, take a read here.
http://backbonejs.org/#Events
As you're already doing, you'll need to listen to some trigger on the collection too
var itemDetails = new app.collections.ItemDetails(); // this is new line
var model2 = itemDetails.fetch(); // here is the problem

backbone and $el element

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.

How to bind existing dom elements to backbone collection view

I have a series of comments on a page, which can be edited. My idea was to render the comments by Rails and preload a json with all those comments in a Backbone Collection.
Then I would poll every x seconds, to see if there are changes. Normally I render the collection by looping over all the models, and create a view for each item. When a model gets updated, so will the view (comment im this case).
But my question is this, how do you bind a view to the model, when the view is already in the DOM. Especially since the view had a dynamic id. There is no point in rendering the view, since it's already there. When you render a view, backbone binds it through somekind of cid.
The only solution I can think of, is by setting an id in the dom object on pageload. iow
<div id="comment-<%= content.id %>"></div>
. And then in the initialize of the view, reset the id
class Comment extends Backbone.View
initialize: ->
#id = "comment-" + #model.get('id')
But I'm not sure if thats the way to go. Would events still be binded?
Special for you :)
var CommentsView = Backbone.View.extend({
tagName : 'ul',
comments : {},
initialize : function () {
this.listenTo(this.collection, 'add', this.addComment);
this.listenTo(this.collection, 'remove', this.removeComment);
this.listenTo(this.collection, 'change', this.updateComment);
},
addComment : function (model) {
this.comments[model.id] = new CommentView({model:model});
this.$el.append(this.comments[model.id].render().$el);
},
removeComment : function (model) {
this.comments[model.id].remove();
this.comments[model.id] = null;
},
updateComment : function (model) {
this.comments[model.id] = new CommentView({model:model});
this.$('[data-id="' + model.id + '"]').before(this.comments[model.id].render().$el).remove();
}
});
var CommentView = Backbone.View.extend({
tagName : 'li',
template : _.template('<div data-id="<%= id %>"><%= name %>: <%- message %></div>'),
render : function () {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
// comments
var initialComments = [{id:1, name:'user1', message:'great!'}, {id:2, name:'user2', message:':)'}];
var actualComments = [{id:1, name:'user1', message:'great! [edited]'}];
var comments = new Backbone.Collection();
var commentsView = new CommentsView({collection:comments});
// show comments
commentsView.render().$el.appendTo('body');
// simulate fetch
comments.add(initialComments);
// simulate update
_.delay(function() {
comments.update(actualComments);
},
2000);
jsfiddle

View Events not firing on created elements?

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>

Categories