View is not getting rendered backbonejs - javascript

I have the following backbonejs code which fetches data in collection from my API.
app.js
var app = app || {};
app.Book = Backbone.Model.extend({
defaults: {
photo: 'https://tpc.googlesyndication.com/simgad/10102369525962162085'
}
});
app.Library = Backbone.Collection.extend({
model: app.Book,
url: 'http://localhost:8000/api/v1/products_user',
parse: function (response) {
return response.data;
}
});
app.BookView = Backbone.View.extend({
tagName: 'article',
className: 'white-panel',
template: _.template( $( '#product_grid_template' ).html() ),
render: function() {
//this.el is what we defined in tagName. use $el to get access to jQuery html() function
this.$el.html( this.template( this.model.attributes ) );
return this;
}
});
app.LibraryView = Backbone.View.extend({
el: '#pinBoot',
initialize: function() { // UPDATED
this.collection = new app.Library(); // UPDATED
this.collection.fetch({
reset:true});
this.listenTo( this.collection, 'add', this.renderBook );
this.listenTo( this.collection, 'reset', this.render ); // NEW
},
// render library by rendering each book in its collection
render: function() {
console.log('render');
this.collection.each(function( item ) {
this.renderBook( item );
}, this );
},
// render a book by creating a BookView and appending the
// element it renders to the library's element
renderBook: function( item ) {
var bookView = new app.BookView({
model: item
});
this.$el.append( bookView.render().el );
}
});
new app.LibraryView();
The above code renders the following HTML
<article class="white-panel r1 c0" style=
"width: 316.667px; left: 0px; top: 0px;">
<div class="widget portfolio graphics homepage">
<div class="entry-container span4">
<!-- Portfolio Image -->
<div class="entry-image">
<a href="product.html"><img alt="" src=
"https://tpc.googlesyndication.com/simgad/10102369525962162085"></a>
</div>
<div class="entry drop-shadow curved">
<!-- Portfolio Heading -->
<div class="heading">
My product HII
</div>
<div class="camera" data-target="#prod0-modal" data-toggle=
"modal">
3
</div>
<div class="price">
₹ 10 <span>+ Delivery Charges ₹ 11</span>
</div>
</div>
<div class="clearfix"></div>
</div>
</div>
</article>
The whole HTML and data was getting rendered to the HTML and I found the why the was not visible was because, a style parameter 'style="opacity: 1;"' is not getting added to the <div class="entry-container span4">
The above app.js , I have modified to work, without an API as follows
app.LibraryView = Backbone.View.extend({
el: '#pinBoot',
initialize: function(initialBooks) {
// this.collection = new app.Library();
// this.collection.fetch({
// reset:true});
// this.listenTo( this.collection, 'add', this.renderBook );
// this.listenTo( this.collection, 'reset', this.render ); // NEW
this.collection = new app.Library( initialBooks );
this.render();
},
// render library by rendering each book in its collection
render: function() {
console.log('render');
this.collection.each(function( item ) {
this.renderBook( item );
}, this );
},
// render a book by creating a BookView and appending the
// element it renders to the library's element
renderBook: function( item ) {
var bookView = new app.BookView({
model: item
});
this.$el.append( bookView.render().el );
}
});
var books = [
{ product_name: 'My product',delivery_charge: '10.00',amount:'10.22' },
];
new app.LibraryView(books);
Now everything works perfectly, the style gets added to the div without any problems. The style is getting added using a fade effect javascript which is suppose to give a fadein effect to the grids, but when I fetch data and renders using the API , the div doesn't get added by the style element.
Why is this happening? Is it something to do with the pageload rendering or asynchronous data loading?
I am new to backbonejs, can someone helps me whats going wrong here when I call the API?
UPDATE
I have this script which is adding the opacity element to the div for visiblity
<script type="text/javascript">
//<![CDATA[
$(window).load(function() { // makes sure the whole site is loaded
$("#status").fadeOut(); // will first fade out the loading animation
$("#preloader").delay(350).fadeOut("slow"); // will fade out the white DIV that covers the website.
$("#preloader-container").css("position","static");
$(".widget .entry-container").each(function(index) {
$(this).delay(400*index).animate({'opacity': 1},300);
});
});
//]]>
</script>
Is this script conflicting with my backbone data rendering?

You are fetching your data before you bind the event for handling it. If the fetch ist fast enough this might cause a race condition.
initialize: function() { // UPDATED
this.collection = new app.Library(); // UPDATED
this.collection.fetch({
reset:true});
this.listenTo( this.collection, 'add', this.renderBook );
this.listenTo( this.collection, 'reset', this.render ); // NEW
}
After setting up the views collection, bind all the necessary events and then do anything with it:
initialize: function() { // UPDATED
this.collection = new app.Library(); // UPDATED
this.listenTo( this.collection, 'add', this.renderBook );
this.listenTo( this.collection, 'reset', this.render ); // NEW
this.collection.fetch({
reset:true});
}

Related

Attach View of Model to existing DIV in Backbone

need help, can't understand how to attach each View of the model to each already existing DIV in DOM ( have and div.container with div.widget array ).
// Model
V.Models.Shortcode = Backbone.Model.extend({});
// Shortcodes Collection Init
V.Collections.Shortcodes = Backbone.Collection.extend({
model: V.Models.Shortcode,
});
When Iframe load, push storage from server to collection:
$('#preview').on('load', function() {
var ShortcodesCollection = new V.Collections.Shortcodes( Vision.ShortcodeStorage );
var Preview = new V.Views.Preview({
collection: ShortcodesCollection,
el: $('.container')
});
Preview.render();
});
Render Preview with collection:
// Collection View in iframe
V.Views.Preview = Backbone.View.extend({
initialize: function() {
this.collection.on('add', this.addOne, this);
},
render: function() {
this.collection.each(this.addOne, this);
return this;
},
addOne: function(ad) {
var shortcodeView = new V.Views.Shortcode({ model: ad });
shortcodeView.render();
}
});
View for each Model:
// Shortcode View
V.Views.Shortcode = Backbone.View.extend({
events: {
'click .widget' : 'SomeActionOnView'
},
render: function(){
//console.log(this.el);
//console.log(this.model.toJSON());
},
SomeActionOnView: function(){
console.log(this);
}
});
Question is, how to attach V.Views.Shortcode to each div with "widget" class to bind events. Thanks!
Can you please try this?
V.Views.Shortcode = Backbone.View.extend({
events: {
'click .widget' : 'SomeActionOnView'
},
render: function(){
//code here to write stuff of this.$el
$("div.widget").append(this.$el);
},
SomeActionOnView: function(){
console.log(this);
}
});

Simple Backbone.js collection infinite paging

My server-side api follows a classic results-paging model, e.g.
/api/transactions/ => page 1 (10 items default limit)
/api/transactions/?p=2 => page 2
I want to build a infinite-scrolling system with Backbone views.
I already have non-paging collection+views setup. The parent view looks like this:
Backbone.View.extend({
initialize: function() {
this.collection = TransactionCollection;
this.fetch();
this.listenTo( this.collection, 'reset', this.renderEntries );
this.listenTo( this.collection, 'add', this.fetch );
this.rowViews = [];
this.render();
},
fetch: function() {
this.collection.fetch({ reset:true });
},
render: function() {
this.$el.html( template() );
this.renderEntries();
return this;
},
renderEntries: function() {
this.clearEntryRows();
this.collection.each(function(item) {
var row = new TransactionItemView( item );
this.rowViews.push( row );
this.$el.find('.entry-list').append( row.render().el );
}, this);
},
clearEntryRows: function() {
this.rowViews.forEach(function(v) {
if (v.close) v.close();
});
this.rowViews = [];
},
// ...
}
This is the relevant part of the view code (child views are simple item views, rendering themselves with a template).
The collection is still very basic:
var TransactionCollection = Backbone.Collection.extend({
model: Transaction,
url: '/api/transactions'
});
Now it's time to add pagination. I think I'm going to add a button "MORE...", after each renderEntries() call. That button will fetch for the next page (without resetting the collection) and another renderEntries is called. The this.clearEntryRows() will be moved to the reset callback.
My question is: how can I fetch the second page and add models without resetting the collection and intercept just that event, to render next entry pages?
I've read something about 'sync' event: in my understanding, 'reset' gets fired only when I fetch with reset:true, 'sync' gets fired every time I fetch the collection, anyway.
So, if this is correct, I can clear entry rows only on reset event and display rows in sync. But how can I display only the newly added (e.g. page 2) rows to my list?
I'm a little confused.
this.collection.fetch({ add: true, remove: false, merge: false, data: {p: 2} });
this allow you to fetch with specified get parameters and only add not existing entries to collection.
In your view:
initialize: function () {
this.listenTo(this.collection, 'add', handlerForRenderingNewEntries);
}
To render only new models, you can return them with specified attribute, extra property 'page' for example. Filter them by this attribute and send to rendrer.

Model is not deleted from backbone collection

I still trying to remove (destroy) model from my collection. Data is groupedBy and rendered into accordion style. But when I click to X in my console is notice :
Uncaught Uncaught TypeError: Cannot call method 'destroy' of undefined
(function() {
window.App = {
Models: {},
Collections: {},
Views: {},
Router: {}
};
window.vent = _.extend({}, Backbone.Events);
})();
// !models.js
App.Models.Item = Backbone.Model.extend({});
// !collections.js
App.Collections.Items = Backbone.Collection.extend({
model: App.Models.Item,
url: 'api/items.json'
});
// !views.js
App.Views.Items = Backbone.View.extend({
el: '#items',
events: {
'click .cccc':'deleteItem',
},
deleteItem: function() {
this.model.destroy();
},
initialize: function() {
this.listenTo( this.collection, "change", this.render );
this.template = _.template( document.getElementById('productsCategoriesTemlate').innerHTML );
this.render();
this.$el.accordion({ animate: 0 });
},
getGroups : function(){
return _.groupBy(this.collection.toJSON(), 'category');
},
render: function() {
this.el.innerHTML = this.template({ data : this.getGroups() });
},
addOne: function(item) {
// ????????????
}
});
App.Views.Item = Backbone.View.extend({
deleteItem: function() {
this.model.destroy();
},
// ???????????
});
// !router.js
App.Router = Backbone.Router.extend({
routes: {
'':'index',
},
index: function() {
console.log('index page !');
},
});
new App.Router;
Backbone.history.start();
App.items = new App.Collections.Items;
App.items.fetch().then(function() {
new App.Views.Items({ collection: App.items });
});
template :
<script id="productsCategoriesTemlate" type="text/template">
<% _.each( data, function( category, i ){ %>
<h3 class="category-name"><%= i %></h3>
<div><% _.each( category, function( item ){ %>
<li class="product"><%= item.title %><p style="float:right;" class="cccc">X</p></li>
<% }) %>
</div>
<% }) %>
</script>
Where do you instantiate Apps.Views.Items? Is this your 'collection view'? If this view is representing your collection, you have to somehow pass or reference the model on 'deleteItem'.
App.Views.Items does not represent a single model, so this.model would be incorrect.
UPDATE
You should have a separate view for each item, such as App.Views.Item, and loop through and create this view for each model in App.Views.Items' collection.
2nd UPDATE
Yeah, you are getting it. Here's some sample code I threw together (I haven't tested it, so you might have to adjust it, but it gives a good idea. The template rendering syntax might be incorrect as I don't usually do it manually).
App.Views.Items = Backbone.View.extend({
render: function() {
this.collection.each(function(model) {
var view = new App.Views.Item({ model: model });
this.$el.append(view.render().el);
});
},
});
App.Views.Item = Backbone.View.extend({
template: _.template($('#itemViewTemplate')),
render: function() {
this.$el.html(this.template(this.model.toJSON()));
},
});
App.items = new App.Collections.Items;
App.items.fetch().then(function() {
var items = new App.Views.Items({ collection: App.items });
$('body').append(items.render().$el);
});
By the way, once you get the hang of Backbone and how it works, you should try out Marionette.js. It makes all of this kind of thing much simpler.

Loading bar in Backbone

I want to show a loading message/icon until all the items in the list have been rendered.
Here is the jsfiddle with my example: http://jsfiddle.net/9R9zU/58/
I've tried to add a div with a loading bar in the Feed section, but it doesn't work.
How can I show a loading message before all the book views are rendered in the book list view:
app.BookListView = Backbone.View.extend({
el: '.feed',
initialize: function() {
this.render();
this.listenTo( this.collection, 'add', this.renderBook );
In theory you need to fetch some content asynchronously from somewhere to display the loader. A loading is needed to show the user that you are actually fetching the content and that the UI is not dead.
In that fiddle even if you got it working you won't be able to see it because the collection is bootstrapped and you are not fetching anything.
This simulates that (updated your fiddle):
app.BookListView = Backbone.View.extend({
el: '.feed',
initialize: function() {
this.loader();
this.listenToOnce( this.collection, 'sync', this.render); // or listenTo ?
this.listenTo( this.collection, 'add', this.renderBook );
// this simulates the fetching...
// It's not really needed
var self = this;
setTimeout(function(){
self.collection.trigger('sync');
}, 3000)
},
loader: function(){
this.$el.html('<div>Loading...</div>')
},
render: function() {
this.$el.empty();
this.collection.each(function( item ){
this.renderBook( item );
}, this);
},
renderBook: function ( item ) {
var bookview = new app.BookView ({
model: item
});
this.$el.append( bookview.render().el );
}
});
Here's a working example: http://jsfiddle.net/aJfUx/1/
render: function() {
// Make this loading icon/message whatever you want
this.$el.html("<i class='icon-spin icon-refresh loading-icon' />");
this.collection.each(function( item ){
this.renderBook( item );
}, this);
this.$el.find(".loading-icon").remove();
}
And here's an example that uses setTimeout to artificially add some loading time so you can see that spinner spin!
http://jsfiddle.net/7ddXM/

How do I render a Backbone Collection in a List and Item View?

I am working on a contact bar which renders all contacts of a user in a html list.
What I have:
UserModel - This is a simple Backbone.Model with username and email
UserCollection - This is used as the contact list
ContactsView - This is the ul contact list
ContactView - This is a single contact model rendered as li
I am currently breaking my head about a solution how (and where) I can fetch my UserCollection and how I pass the single models down to a single ContactView item.
Specific hurdles are:
Where should I fetch, store the UserCollection
How do I render the contact list
How do I render the contact items
How do I prevent fetch({ success: callback }) from breaking my code structure
My current code is this:
entrance point:
// create a new instance of the contact list view
var view = new ContactsView();
// insert the rendered element of the contact list view in to the dom
$('div.contacts-body').html(view.render().el);
view.fetch({ success: view.loadContacts });
ContactsView:
define(
['jquery', 'underscore', 'backbone', 'text!templates/conversations/contacts.html', 'collections/users', 'views/conversations/contact'],
function($, _, Backbone, ContactsTemplate, UserCollection, ContactView) {
var ContactsView = Backbone.View.extend({
tagName: "ul",
className: "contacts unstyled",
attributes: "",
// I am feeling uneasy hardcoding the collection into the view
initialize: function() {
this.collection = new UserCollection();
},
// this renders our contact list
// we don't need any template because we just have <ul class="contacts"></ul>
render: function() {
this.$el.html();
return this;
},
// this should render the contact list
// really crappy and unflexible
loadContacts: function() {
this.collection.each(function(contact) {
// create a new contact item, insert the model
var view = new ContactView({ model: contact });
// append it to our list
this.$el.append(view.render().el);
});
}
});
return ContactsView;
});
ContactView
define(
['jquery', 'underscore', 'backbone', 'text!templates/conversations/contact.html'],
function($, _, Backbone, ContactTemplate) {
var ContactView = Backbone.View.extend({
tagName: "li",
className: "contact",
attributes: "",
template:_.template(ContactTemplate),
initialize: function() {
this.model.bind('change', this.render, this);
this.model.bind('destroy', this.remove, this);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
return ContactView;
});
Could somebody help me about my four hurdles.
Good example links are welcome. I oriented my code style at the todos list unfortunatly the todos list isn't that advanced...
UPDATED CODE:
define(
['jquery', 'underscore', 'backbone', 'text!templates/conversations/contacts.html', 'collections/users', 'views/conversations/contact'],
function($, _, Backbone, ContactsTemplate, UserCollection, ContactView) {
var ContactsView = Backbone.View.extend({
tagName: "ul",
className: "contacts unstyled",
attributes: "",
events: {
},
initialize: function() {
this.collection = new UserCollection();
this.collection.on('reset', this.render);
this.collection.fetch();
},
render: function() {
// in chromium console
console.log(this.el); // first: html, second: undefined
console.log(this.$el); // first: html in array, second: undefined
this.$el.empty(); // error on the called that this.$el is undefined
this.collection.each(function(contact) {
var view = new ContactView({ model: contact });
this.$el.append(view.el);
}.bind(this));
return this;
}
});
return ContactsView;
Can it be that reset is triggering this.render twice?
First of all: why do you fetch the view? Backbone views do not have a fetch method..
1 The correct place to fetch your UserCollection would be inside the view's initialize method:
initialize: function() { // ContactsView
_.bindAll(this, 'render', 'otherMethodName', ...); // Bind this to all view functions
...
this.collection.on('reset', this.render); // bind the collection reset event to render this view
this.collection.fetch();
...
}
Now you fetch the contacts exactly when you need them. Next step is to render the collection.
2 Binding to the reset event makes your loadContacts method obsolete and we can do that in the render function:
render: function() {
this.$el.empty(); // clear the element to make sure you don't double your contact view
var self = this; // so you can use this inside the each function
this.collection.each(function(contact) { // iterate through the collection
var contactView = new ContactView({model: contact});
self.$el.append(contactView.el);
});
return this;
}
Now you render your contactlist inside the render method, where it should be done.
3 The ContactView actually looks good.
Just make the item to render itself in the initialize method, so you don't have to make useless calls in the ContactsView's render method and clutter up your code. Also bindAll here as well.
initialize: function() { // ContactView
_.bindAll(this, 'render', 'otherMethodName', ...);
...
this.render(); // Render in the end of initialize
}
I have no idea what you are asking in here, but I think the best way is not to use success callbacks. The collections and models trigger events whenever something is done to them, so tapping onto them is much more robust and reliable than success callbacks. Check out the catalog of events to learn more. The Wine Cellar tutorial by Christophe Coenraets is has an excellent example of this kind of listview-listitemview arrangement.
Hope this helps!
UPDATE: Added _.bindAlls to fix the problem with this in a event bound render call. Some info on binding this.
NOTE: all the code is simplified and no tested
When I have all the elements structure defined, as you have, with all the Models, Collections and Views implemented then I implement a Loader which is in charge of trigger the fetching and rendering actions.
First of all I need to expose the classes definition from the outside something like this:
// App.js
var App = {}
// ContactsCollection.js
$(function(){
var App.ContactsCollection = Backbone.Collection.extend({ ... });
});
// ContactsView.js
$(function(){
var App.ContactsView = Backbone.View.extend({ ... });
});
// and so on...
And then I implement what I call the Loader:
// AppLoad.js
$(function(){
// instantiate the collection
var App.contactsCollection = new App.ContactsCollection();
// instantiate the CollectionView and assign the collection to it
var App.contactsView = new App.ContactsView({
el: "div.contacts-body ul",
collection: App.contactsCollection
});
// fetch the collection the contactsView will
// render the content authomatically
App.contactsCollection.fetch();
});
Another changes you have to do is configure the ContactsView in a way that respond to the changes in the App.contactsCollection because as the fetch() is asynchronous you can call render() when the collection is still not loaded, so you have to tell to the CollectionView to render it self when the Collection is ready:
var ContactsView = Backbone.View.extend({
initialize: function( opts ){
this.collection.on( 'reset', this.addAll, this );
this.collection.on( 'add', this.addOne, this );
// ... same with 'remove'
},
addOne: function( model ){
var view = new App.ContactView({ model: contact });
this.$el.append( view.render().el );
},
addAll: function(){
this.collection.each( $.proxy( this.addOne, this ) );
}
});
You have to require your js files in the proper order:
App.js
Your Models, Collections, Views
AppLoad.js
With this system you obtain:
External access to your collection in case you need to access it from another place.
External control of the CollectionView.el with is better for decoupling and testing.
The CollectionView will respond to changes in the Collection authomatically
Note: If you use Router you can move the AppLoad.js logic to there.

Categories