Backbone change event is not firing - javascript

I am learning Backbone and struck at one place. I wanted to load model on page load. So I have below code.
<html>
<head>
<script src="js/jquery.min.js"></script>
<script src="js/underscore-min.js"></script>
<script src="js/backbone-min.js"></script>
<script src="js/json2.js"></script>
</head>
<body>
<h1></h1>
<div id="poview"></div>
<script id="poTemplate" type="text/template">
<td><%= Ponumber %></td>
<td><%= Posuppliername %></td>
<td><%= Postatusname %></td>
<td><%= Podate %></td>
<td><%= DispOrderTotal %></td>
</script>
<script type="text/javascript">
(function($) {
var PO = Backbone.Model.extend()
var POList = Backbone.Collection.extend({
url: 'po.json',
model: PO,
parse: function(response) {
console.log(response.PurchaseOrder)
return response.PurchaseOrder
}
})
var POView = Backbone.View.extend({
tagName: 'tr',
template: $('#poTemplate').html(),
initialize: function() {
_.bindAll(this, 'render')
this.model.bind('change', this.render)
},
events: {
'click': 'click'
},
render: function() {
console.log('po render')
var tmpl = _.template(this.template)
$(this.el).html(tmpl(this.model.toJSON()))
return this;
},
click: function() {
console.log('clicked....')
}
})
var POListView = Backbone.View.extend({
el: $('#poview'),
initialize: function() {
_.bindAll(this, 'render', 'appendPO')
this.collection = new POList()
this.collection.bind('change', this.render, this)
this.collection.bind('add', this.render, this)
this.collection.fetch({add:true})
},
render: function() {
var self = this;
console.log(this.collection.models)
this.collection.each(function(po) {
self.appendPO(po)
}, this)
},
appendPO: function(po) {
var poView = new POView({
model: po
});
console.log(po)
$(this.el).append(poView.render().el)
}
});
var poListView = new POListView()
})(jQuery)
</script>
</body>
</html>
And below is json response from server
{
"#totalCount": "1134",
"PurchaseOrder": [
{
"ID": "689600",
"Ponumber": "K037412201",
"Poname": "",
"Podate": "2011-12-26T10:03:24.000+05:30",
"Posuppliername": "XYZ UPS Supplier",
"Postatusname": "Approved - Supplier Received",
"DispOrderTotal": "$1.99"
},
{
"ID": "689601",
"Ponumber": "K037412202",
"Poname": "",
"Podate": "2011-12-26T10:03:24.000+05:30",
"Posuppliername": "ABC UPS Supplier",
"Postatusname": "Approved - Supplier Received",
"DispOrderTotal": "$1.99"
}
]
}
But when the page is loaded render method on POListView is not fired. What is issue in this code?
Edit
jQuery(function($){
...
});
If I use above convention also, that does not work.
Working example
Refer answer from #JayC
<html>
<head>
<script src="js/jquery.min.js"></script>
<script src="js/underscore.js"></script>
<script src="js/backbone.js"></script>
<script src="js/json2.js"></script>
</head>
<body>
<h1></h1>
<div id="poview"></div>
<script id="poTemplate" type="text/template">
<td><%= Ponumber %></td>
<td><%= Posuppliername %></td>
<td><%= Postatusname %></td>
<td><%= Podate %></td>
<td><%= DispOrderTotal %></td>
</script>
<script type="text/javascript">
jQuery(function($) {
var PO = Backbone.Model.extend({
idAttribute: 'ID'
})
var POList = Backbone.Collection.extend({
url: 'po.json',
model: PO,
parse: function(response) {
console.log('parse')
return response.PurchaseOrder
}
})
var POView = Backbone.View.extend({
tagName: 'tr',
template: $('#poTemplate').html(),
initialize: function() {
_.bindAll(this, 'render', 'click')
},
events: {
'click': 'click'
},
render: function() {
var tmpl = _.template(this.template)
$(this.el).html(tmpl(this.model.toJSON()))
return this;
},
click: function() {
console.log('clicked.... ' + this.model.id)
}
})
var POListView = Backbone.View.extend({
el: $('#poview'),
initialize: function() {
_.bindAll(this, 'render', 'appendPO')
this.collection = new POList()
this.collection.bind('reset', this.render, this)
this.collection.fetch()
},
render: function() {
var self = this;
console.log(this.collection.models)
this.collection.each(function(po) {
self.appendPO(po)
}, this)
},
appendPO: function(po) {
var poView = new POView({
model: po
});
$(this.el).append(poView.render().el)
}
});
var poListView = new POListView()
});
</script>
</body>
</html>

I'm not seeing a binding to the reset event for your collection. The Backbone.js documentation now has a FAQ which explains events (finally!) and you can see from it that
the reset event fires when the collection's entire contents have been replaced. On collections, that's what fetch normally does.

You wrote this, which executes the function immediately, when the DOM is not yet ready/available:
(function($){
...
})(jQuery);
But probably you wanted to write this (see http://api.jquery.com/jQuery/#jQuery3 ), which delays the function until the document is ready to be manipulated (attach your views, etc):
jQuery(function($){
...
});
Of course, only the actual initialization call should be wrapped into jQuery(), the other parts can remain in the plain closure.

Related

duplicate POST, GET and DELETE request using backbone js

I have a simle CRUD application. But when i do delete , create or edit Nth time N number of request get fired.
For example If i am creating a user 3rd time the 3 POST request gets fired.
when i refresh the page only one request is fired. Please help. below is my code.
HTML
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My App</title>
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/2.1.1/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<h1>User Manager</h1>
<hr />
<div class="page"> </div>
</div>
<script src="http://code.jquery.com/jquery-1.11.0.min.js" type="text/javascript"></script>
<script src="http://underscorejs.org/underscore-min.js" type="text/javascript"></script>
<script src="http://backbonejs.org/backbone-min.js" type="text/javascript"></script>
<script type="text/javascript">
function htmlEncode(value){
return $('<div/>').text(value).html();
}
</script>
<script type="text/template" id="user-list-template">
New
<hr />
<table class="table striped">
<thead>
<tr>
<th>First Name</th><th>Last Name</th><th>Age</th><th></th>
</tr>
</thead>
<tbody>
<% _.each(users, function(user) { %>
<tr>
<td><%= htmlEncode(user.get('firstname')) %></td>
<td><%= htmlEncode(user.get('lastname')) %></td>
<td><%= htmlEncode(user.get('age')) %></td>
<td><a class="btn" href="#/edit/<%= user.id %>">Edit</a></td>
</tr>
<% }); %>
</tbody>
</table>
</script>
<script type="text/template" id="edit-user-template">
<form class="edit-user-form">
<legend><%= user ? 'Edit' : 'New' %> User</legend>
<label>First Name</label>
<input name="firstname" type="text" value="<%= user ? user.get('firstname') : '' %>">
<label>Last Name</label>
<input name="lastname" type="text" value="<%= user ? user.get('lastname') : '' %>">
<label>Age</label>
<input name="age" type="text" value="<%= user ? user.get('age') : '' %>">
<hr />
<button type="submit" class="btn"><%= user ? 'Update' : 'Create' %></button>
<% if(user) { %>
<input type="hidden" name="id" value="<%= user.id %>" />
<button data-user-id="<%= user.id %>" class="btn btn-danger delete">Delete</button>
<% }; %>
</form>
</script>
<script src="user.js" type="text/javascript"></script>
</body>
</html>
JAVASCRIPT
var Myapp ={};
$.ajaxPrefilter( function( options, originalOptions, jqXHR ) {
options.url = 'http://backbonejs-beginner.herokuapp.com' + options.url;
});
// Setting up user collection
Myapp.Users = Backbone.Collection.extend({
url: '/users'
});
// setting up user model
Myapp.User = Backbone.Model.extend({
urlRoot: '/users',
validate: function(attrs, options) {
if (attrs.firstname =='') {
alert("First Name cannot be blank.");
return "First Name cannot be blank.";
}
}
});
// setting up routes
Myapp.Router = Backbone.Router.extend({
routes: {
"": "home",
"edit/:id": "edit",
"new": "edit",
}
});
Myapp.router = new Myapp.Router;
Myapp.router.on('route:home', function() {
// render user list
Myapp.displayUsersModule.init().render();
})
Myapp.router.on('route:edit', function(id) {
Myapp.editUserViewModule.init().render({id: id});
})
// display user module imkplemented in module patern. The only public api is the init function
Myapp.displayUsersModule = (function(){
var UserListView = Backbone.View.extend({
el: '.page',
render: function () {
var that = this;
var users = new Myapp.Users();
users.fetch({
success: function (users) {
var template = _.template($('#user-list-template').html(), {users: users.models});
that.$el.html(template);
},
error: function (model, xhr, options) {
alert("Something went wrong while fetching details");
}
})
}
});
var init = function() {
userListView = new UserListView();
return userListView;
};
return {
init: init
};
})();
// edit and delete user module imkplemented in module patern. The only public api is the init function
Myapp.editUserViewModule = (function(){
var UserEditView = Backbone.View.extend({
el: '.page',
events: {
'submit .edit-user-form': 'saveUser',
'click .delete': 'deleteUser'
},
saveUser: function (ev) {
var userDetails = $(ev.currentTarget).serializeObject();
var user = new Myapp.User();
user.save(userDetails, {
success: function (user) {
Myapp.router.navigate('', {trigger:true});
},
error: function (model, xhr, options) {
alert("Something went wrong while saving details");
}
});
return false;
},
deleteUser: function (ev) {
alert(1);
this.user.destroy({
success: function () {
console.log('destroyed');
Myapp.router.navigate('', {trigger:true});
},
error: function (model, xhr, options) {
alert("Something went wrong while Deleting user");
}
});
return false;
},
render: function (options) {
var that = this;
if(options.id) {
that.user = new Myapp.User({id: options.id});
that.user.fetch({
success: function (user) {
var template = _.template($('#edit-user-template').html(), {user: user});
that.$el.html(template);
}
})
} else {
var template = _.template($('#edit-user-template').html(), {user: null});
that.$el.html(template);
}
}
});
var init = function() {
var userEditView = new UserEditView();
return userEditView;
};
return {
init: init
};
})();
$(document).ready(function(){
$.fn.serializeObject = function() {
var o = {};
var a = this.serializeArray();
$.each(a, function() {
if (o[this.name] !== undefined) {
if (!o[this.name].push) {
o[this.name] = [o[this.name]];
}
o[this.name].push(this.value || '');
} else {
o[this.name] = this.value || '';
}
});
return o;
};
Backbone.history.start();
});
The problem is that you don't correct unbind views when switching them, so you have multiple zombie view that listen to the same DOM events (it is because you attach them to DOM using el: 'selector' style that is not pretty good).
To fix it you should call remove (or undelegateEvents) method when you don't need it anymore.
Or you can use libraries that help you to manage views such as:
backbone.layoutmanager
backbone.marionette

Show form filled data in div when i clicked on submit button

I am new in backbone. I create a form, Now I want to show data in front end with rest service. my code is:
Template:
<script type="text/template" id="details">
<ul>
<% _.each(persons, function(person) { %>
<li><label>emailId : </label><%= person.emailId.emailId %></li>
<li><%= person.emailId.emailId %></li>
<% }); %>
</ul>
</script>
Model , Collection and View
<script type="text/javascript">
var UserModel = Backbone.Model.extend({});
var EntityList = Backbone.Collection
.extend({
model : UserModel,
url : 'http://192.168.1.3:8080/cofinding/business_profile/searchBusiness/123456789'
});
var View = Backbone.View.extend({
el : '#mydiv',
template : _.template($("#details").html()),
initialize : function() {
var self = this;
this.coll = new EntityList();
this.coll.fetch({
success : function() {
self.render();
}
});
},
render : function() {
// the persons will be "visible" in your template
this.$el.html(this.template({
persons : this.coll.toJSON()
}));
return this;
}
});
var view = new View();
</script>
Above code showing my data from service. But I need when I click on submit button.
Let's say you have the following button on your page:
<button id="submit">Submit</button>
Your View will need to define an events object that tracks what happens when a user clicks on your button:
var View = Backbone.View.extend({
events: {
'click #submit': 'fetchEntityList'
},
el: '#myDiv',
//etc...
});
You can then define the function that is executed. It should probably do something similar to what you currently have in initialize:
fetchEntityList: function() {
var self = this;
this.coll.fetch({
success : function() {
self.render();
}
});
}
The fetchEntityList function will now be executed whenever a user clicks Submit. It will fetch your EntityList collection and render it on the page.
hello guys i got my result what i want:
<body>
<div class="container">
<h1>User name</h1>
<hr>
<div class="page"></div>
</div>
<script type="text/template" id="edit-user-template">
<table border="1" cellpadding="4">
<tr>
<th>Email Id</th>
<th>Is Verified</th>
</tr>
<% _.each(users, function(user) { %>
<tr>
<td><%= user.get('emailId').emailId %></td>
<td><%= user.get('emailId').isVerified %></td>
</tr>
<tr>
<td><%= user.get('emailId').emailId %></td>
<td><%= user.get('emailId').isVerified %></td>
</tr>
<% }); %>
</table>
</script>
<script>
var Users = Backbone.Collection
.extend({
url : 'http://192.168.1.3:8080/app/business_profile/searchBusiness/123456789'
});
var UserList = Backbone.View.extend({
el : '.page',
render : function() {
var that = this;
var users = new Users();
users.fetch({
success : function() {
var template = _.template($('#edit-user-template')
.html(), {
users : users.models
});
that.$el.html(template);
}
})
}
});
var Router = Backbone.Router.extend({
routes : {
'' : 'home'
}
});
var userList = new UserList();
var router = new Router();
router.on('route:home', function() {
userList.render();
//console.log('we have loaded the home page');
});
Backbone.history.start();
</script>

Template with backbone.js

Having a simple template with backbone.js -
jsTemplateBackbone.html
<html>
<head>
<script src="jquery-2.1.0.min.js"></script>
<script src="json2.min.js"></script>
<script src="underscore.js"></script>
<script src="backbone.js"></script>
<!--Order is important !!!-->
</head>
<body>
<script type="text/template" id="item-container">
<li><%= value %></li>
</script>
<script src="jsBackboneHomeView.js"></script>
</body>
</html>
jsBackboneHomeView.js
//Model
Wine = Backbone.Model.extend();
// Collection
Wines = Backbone.Collection.extend({
Model: Wine,
url: "#"
});
// Collection object
wines = new Wines([
{ name: "Robert Mondovi" },
{ name: "CakeBread" }
]);
// List
ListView = Backbone.View.extend({
tagName: 'ul',
initialize: function () {
// grabing the html template
this.template = _.template($('#item-container').html());
},
render: function () {
var el = this.$el, template = this.template;
el.empty();
wines.each(function (wine) {
el.append(template(wine.toJSON()));
});
return this;
}
});
// View
HomeView = Backbone.View.extend({
el: 'body',
initialize: function () {
this.render();
},
render: function () {
this.$el.empty();
this.$el.append("<h1>My App</h1>");
listView = new ListView();
// append two <li> on <ul> and return it
this.$el.append(this.listView.render().el);
return this;
}
});
// View instance
wineApp = new HomeView();
when I execute it , it gives an error -
Uncaught TypeError: Cannot call method 'replace' of undefined - underscore.js:1236
line 1236 - text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
What is wrong here ?
(The code taken from this tutorial . )
The property in your model is called name while it is value in your template, and thus it can't be replaced. Try :
<li><%= name %></li>
in your template.
in your ListView render function, instead of returning this, return the values you want to be available as an object,
e.g.
return({
el: el
});

Backbone groupedBy collection is rendered only after one event click

I have problem with my groupedBy collections because is not rendered after second event click. I think there is problem with fetch my collection...but I don't know how it fix. There is my code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Backbone test</title>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0-rc1/css/bootstrap.min.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
ul.items{list-style: none;width: 100%;margin: 0px;-webkit-margin-before: 0em;-webkit-padding-start: 0px;}
</style>
</head>
<body>
<header>
</header>
<content id="content">
<div id="persons"></div>
<div id="items"></div>
<div id="orders"></div>
</content>
<footer>
</footer>
<script id="itemsTemlate" type="text/template">
<div class="jumbotron">
<div class="container">
<h1>My Items!</h1>
</div>
<button class="open-orders" style="float:left;">Orders</button>
<button class="open-persons" style="float:right;">Persons</button>
</div>
<% _.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">-</p><p style="float:right;" class="cccc">+</p></li>
<% }) %>
</div>
<% }) %>
</script>
<script id="ordersTemlate" type="text/template">
<div class="jumbotron">
<div class="container">
<h1>My Orders!</h1>
</div>
<button class="open-items" style="float:left;">Items</button>
<button class="open-persons" style="float:right;">Persons</button>
</div>
<% _.each( data, function( category, i ){ %>
<h3 class="category-name"><%= i %></h3>
<div><% _.each( category, function( order ){ %>
<li class="order"><%= order.title %><p style="float:right;" class="cccc">X</p></li>
<% }) %>
</div>
<% }) %>
</script>
<script id="personsTemlate" type="text/template">
<div class="jumbotron">
<div class="container">
<h1>My Persons!</h1>
</div>
<button class="open-items" style="float:left;">Items</button>
<button class="open-orders" style="float:right;">Orders</button>
</div>
<% _.each( data, function( category, i ){ %>
<h3 class="category-name"><%= i %></h3>
<div><% _.each( category, function( person ){ %>
<li class="product"><%= person.title %><p style="float:right;" class="cccc">X</p></li>
<% }) %>
</div>
<% }) %>
</script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.5.1/underscore-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone-min.js"></script>
<script>
(function() {
window.App = {
Models: {},
Collections: {},
Views: {},
Router: {}
};
window.vent = _.extend({}, Backbone.Events);
})();
// !models.js
App.Models.Item = Backbone.Model.extend({});
App.Models.Person = Backbone.Model.extend({});
App.Models.Order = Backbone.Model.extend({});
// !collections.js
App.Collections.Items = Backbone.Collection.extend({
model: App.Models.Item,
url: 'api/items.json'
});
App.Collections.Persons = Backbone.Collection.extend({
model: App.Models.Person,
url: 'api/persons.json'
});
App.Collections.Orders = Backbone.Collection.extend({
model: App.Models.Order,
url: 'api/orders.json'
});
// !views.js
App.Views.Items = Backbone.View.extend({
el: '#items',
events: {
'click button.open-orders':'openOrders',
'click button.open-persons':'openPersons',
},
openOrders: function() {
this.remove();
this.unbind();
App.myRouter.navigate("orders", {trigger: true, replace: true});
console.log("openOrders");
},
openPersons: function() {
this.remove();
this.unbind();
App.myRouter.navigate("persons", {trigger: true, replace: true});
console.log("openPersons");
},
initialize: function() {
this.listenTo( this.collection, "change", this.render );
this.template = _.template( document.getElementById('itemsTemlate').innerHTML );
this.render();
this.$el;
},
getGroups : function(){
return _.groupBy(this.collection.toJSON(), 'category');
},
render: function() {
this.el.innerHTML = this.template({ data : this.getGroups() });
},
});
App.Views.Persons = Backbone.View.extend({
el: '#persons',
events: {
'click button.open-items':'openItems',
'click button.open-orders':'openOrders',
},
openItems: function() {
this.remove();
this.unbind();
App.myRouter.navigate("items", {trigger: true, replace: true});
},
openOrders: function() {
this.remove();
this.unbind();
App.myRouter.navigate("orders", {trigger: true, replace: true});
},
initialize: function() {
this.listenTo( this.collection, "change", this.render );
this.template = _.template( document.getElementById('personsTemlate').innerHTML );
this.render();
this.$el;
},
getGroups : function(){
return _.groupBy(this.collection.toJSON(), 'category');
},
render: function() {
this.el.innerHTML = this.template({ data : this.getGroups() });
},
});
App.Views.Orders = Backbone.View.extend({
el: '#orders',
events: {
'click button.open-items':'openItems',
'click button.open-persons':'openPersons',
},
openItems: function() {
this.remove();
this.unbind();
App.myRouter.navigate("items", {trigger: true, replace: true});
},
openPersons: function() {
this.remove();
this.unbind();
App.myRouter.navigate("persons", {trigger: true, replace: true});
},
initialize: function() {
this.listenTo( this.collection, "change", this.render );
this.template = _.template( document.getElementById('ordersTemlate').innerHTML );
this.render();
this.$el;
},
getGroups : function(){
return _.groupBy(this.collection.toJSON(), 'category');
},
render: function() {
this.el.innerHTML = this.template({ data : this.getGroups() });
},
});
// !router.js
App.Router = Backbone.Router.extend({
routes: {
'':'persons',
'persons':'persons',
'items':'items',
'orders':'orders'
},
persons: function() {
App.persons = new App.Collections.Persons;
App.persons.fetch().then(function() {
new App.Views.Persons({ collection: App.persons });
});
console.log('persons page !');
},
items: function() {
App.items = new App.Collections.Items;
App.items.fetch().then(function() {
new App.Views.Items({ collection: App.items });
});
console.log('items page !');
},
orders: function() {
App.orders = new App.Collections.Orders;
App.orders.fetch().then(function() {
new App.Views.Orders({ collection: App.orders });
});
console.log('orders page !');
},
});
App.myRouter = new App.Router();
Backbone.history.start();
</script>
</body>
</html>
Sounds like you could use an itemView. This is when marionette comes in very handy.
check out marionette

how to render view from object using backbonejs?

template code :
<script type="text/template" id="baseTemplate">
<% collection.each(function() { %>
<div>
<%= collection %>
</div>
<% }); %>
</script>
<div id="baseContainer"></div>
and other code is :
//model
var PageModel = Backbone.Model.extend({
defaults: {
"id":null,
"title":"",
"content":""
},
});
//collection
var PageCollection = Backbone.Collection.extend({
defaults: {
model: PageModel
},
model: PageModel,
url: 'api/get_page/?id=6'
});
//view
var PageView = Backbone.View.extend({
el: $('#baseContainer'),
initialize: function () {
this.collection = new PageCollection();
this.collection.bind("reset", this.render, this);
this.collection.bind("change", this.render, this);
this.collection.fetch();
},
render: function () {
var html = _.template($("#baseTemplate").html(), { collection: this.collection });
this.$el.html(html);
console.log(html);
return this;
}
});
var page = new PageView();
problem is that its return and object how can i get values from object? api link is http://furqankhanzada.com/backbonejs/api/get_page/?id=6 and here you can see object in browser console http://furqankhanzada.com/backbonejs/
i need to get title, content , attachments -> images -> Gallery Large -> url (attachments using each() ).
Not sure whether this is proper solution or not, but you can give it a try.
An alternative can be like,
var html = _.template($("#baseTemplate").html(), { models: this.collection.models });
Pass models instead of directly passing collection. And in the template you can do something like this,
<script type="text/template" id="baseTemplate">
<% _.each(models, function(mdl) { %>
<div>
<%= mdl.get('title') %>
<%= mdl.get('content') %>
<% _.each(mdl.get('page').attachments, function(attachment) { %>
<%= attachment.images["Gallery Large"].url %>
<% }) %>
</div>
<% }); %>
</script>
<div id="baseContainer"></div>
Please modify the markup as per your needs. But this solution is too specific to the problem :( :(

Categories