Bind multiple models to a single view in backbone - javascript

I have 2 models as
var Info = Backbone.Model.extend({
defaults: {
name: '',
company: ''
},
initialize: function(){
console.log('Object of type Info created');
},
});
var Emp = Backbone.Model.extend({
defaults: {
empId: '',
empGroup: ''
},
initialize: function(){
console.log('Object of type Emp created');
}
});
View is created as
var model = new Info();
model.set({
name: 'John',
company: 'ABC'
});
model.bind('change', function(){
model.save();
});
model.trigger('change');
var ViewClass = Backbone.View.extend({
_modelBinder: undefined,
initialize: function(){
this._modelBinder = new Backbone.ModelBinder();
this.render();
},
render: function(){
var template = _.template($('#App1').html());
this.$el.html(template);
var bindings = {
name: '[name=name]',
empId: '[name=empId]'
};
this._modelBinder.bind(model, this.el, bindings); // this will bind for Info.
}
});
HTML:
<script type="text/template" id="App1">
<div id="wrapper">
Name: <input type="text" name="name" /><br />
EmpId: <input type="text" name="empId" />
</div>
</script>
How can we bind for both Info and Emp Models ?

I dont really know how Backbone.ModelBinder(); work but i suppose that you have to create two binding;
var infoBindings = {
name: '[name=name]',
};
this._modelBinder.bind(infoModel, this.el, infoBindings); // this will bind for Info.
var empBindings = {
empId: '[name=empId]'
};
this._modelBinder.bind(empModel, this.el, empBindings); // this will bind for Emp.

Related

KnockoutJS foreach doesn't iterate through collection

For some reason, foreach in Knockout.js doesn't iterate through my observable array.
In my HTML I have this which works perfectly fine with the observable model:
<div class="field-group">
<label class="popup-label" for="email">Email</label>
<span class="email" data-bind="text: masterVM.employeeVM.Email"></span>
</div>
But in the same model, this code doesn't work:
<ul data-bind="foreach: { data: masterVM.employeeVM.Tags, as: 'tag' }">
<li>
<span class="popup-tag" data-bind="text: tag.tagName"><i class="zmdi zmdi-delete"></i></span>
</li>
</ul>
There are two models:
Employee
var observableEmployee = function(id, email, tags) {
var self = this;
self.Id = ko.observable(id);
self.Email = ko.observable(email);
self.Tags = ko.observableArray(ko.utils.arrayMap(tags, function(item) {
return new observableTag(item.Id, item.EmployeeId, item.TagId, item.tagName)
}));
self.errors = ko.validation.group(this, {
deep: true
});
self.isValid = ko.computed(function() {
return self.errors().length > 0 ? false : true;
});
}
and Tag
var observableTag = function(id, employeeId, tagId, tagName) {
var self = this;
self.Id = ko.observable(id);
self.employeeId = ko.observable(employeeId);
self.tagId = ko.observable(tagId);
self.TagName = ko.observable(tagName);
self.errors = ko.validation.group(this, {
live: true
});
self.isValid = ko.computed(function() {
return self.errors().length > 0 ? false : true;
});
}
and handler function:
var employeeHandler = function () {
var self = this;
self.getEmployeeDetails = function (header) {
$.ajax({
url: masterVM.controller.renderEmployeeDetails,
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify({ id: header.data("employeeid") }),
success: function (result) {
masterVM.employeeVM = new observableEmployee(
result.model.Id,
result.model.Email,
result.model.Tags
);
ko.applyBindings(masterVM, $("#employee-planning-selected")[0]);
//header.parent().addClass('open');
//header.next().slideDown('normal');
//hideLoader(header);
console.log('get employee details');
$(document).on('click', "div.employee", onNameCardClick);
},
error: function (xhr, ajaxOptions, thrownError) {
alert('Error!');
}
});
}}
In my HTML file
<script>
masterVM = {
controller: {
renderEmployeeDetails: '#(Html.GetActionUrl<EmployeesController>(c => c.RenderEmployeeDetails(0)))'
},
employeeHandler: new employeeHandler(),
employeeVM: new observableEmployee(0, '', '', '', '')
}
ko.applyBindings(masterVM);
</script>
Tried something like this, and still nothing
<!--ko foreach: employeeVM.Tags -->
<span data-bind="text: $data.Tags"></span>
<!-- /ko -->
And no, there are no errors in the console, I have used KnockouJS context debugger which shows me that there are elements in this collection, even when I try to display them as an object it shows me a list of 4 elements.
Knockout version: 2.3.0
1). If you are binding masterVM object in ko.applyBindings(masterVM), you don't need to specify that object again in your data-bindings.
So, it should be
foreach: { data: employeeVM.Tags, as: 'tag' }
And not
foreach: { data: masterVM.employeeVM.Tags, as: 'tag' }
(I'm not sure how the first data-bind="text: masterVM.employeeVM.Email" is working)
2). You don't need to call applyBindings more than once. If you want to update the employee object, you can turn your employeeVM into an observable and keep updating it inside getEmployeeDetails method.
3) Your containerless control flow syntax won't work. (<!--ko foreach: employeeVM.Tags -->). Inside this foreach, $data is the current Tag object in context. So, it should be <span data-bind="text: $data.TagName"></span>
Here's a minimal version of the code. Click on "Run code snippet" to test it. When you click on Update employee button, I'm updating the employeeVM observable and the data gets rendered again. Without calling applyBindings again
var employeeHandler = function() {
var self = this;
self.getEmployeeDetails = function(header) {
var newEmployee = new observableEmployee(0, 'newEmployee#xyz.com', [{
Id: 3,
EmployeeId: 3,
TagId: 3,
tagName: 'Tag Name 3'
}]);
// You need to use employeeVM(newEmployee) instead of employeeVM = newEmployee
// Because employeeVM is an observable.
masterVM.employeeVM(newEmployee);
}
}
var observableEmployee = function(id, email, tags) {
var self = this;
self.Id = ko.observable(id);
self.Email = ko.observable(email);
self.Tags = ko.observableArray(ko.utils.arrayMap(tags, function(item) {
return new observableTag(item.Id, item.EmployeeId, item.TagId, item.tagName)
}));
}
var observableTag = function(id, employeeId, tagId, tagName) {
var self = this;
self.Id = ko.observable(id);
self.employeeId = ko.observable(employeeId);
self.tagId = ko.observable(tagId);
self.TagName = ko.observable(tagName);
}
var masterVM = {
controller: {
renderEmployeeDetails: ''
},
employeeHandler: new employeeHandler(),
// change this to an observable
employeeVM: ko.observable(new observableEmployee(0, 'abc#xyz.com', [{
Id: 1,
EmployeeId: 1,
TagId: 1,
tagName: 'Tag name 1'
}]))
}
ko.applyBindings(masterVM);
document.getElementById("button").addEventListener("click", function(e) {
masterVM.employeeHandler.getEmployeeDetails()
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div class="field-group">
<label class="popup-label" for="email">Email:</label>
<span class="email" data-bind="text: employeeVM().Email"></span>
</div>
<ul data-bind="foreach: { data: employeeVM().Tags, as: 'tag' }">
<li>
<span class="popup-tag" data-bind="text: tag.employeeId"></span> <br>
<span class="popup-tag" data-bind="text: tag.tagId"></span><br>
<span class="popup-tag" data-bind="text: tag.TagName"></span>
</li>
</ul>
<button id="button">Update employee</button>

how to populate backbone collection from php backend

I have the backbone code below which shows a list of models on a html page. However the models are created staticly, what modifications do I have to make on the code below to fetch the JSON data and create models based on the data received.
var Person = Backbone.Model.extend({
defaults: {
name: "John Doe",
age: 27,
designation: "worker"
},
initialize : function(){
this.on("invalid",function(model,error){
alert(error);
});
},
validate: function(attrs){
if(attrs.age < 0){
return 'Age must be positive,stupid';
}
if( ! attrs.name ){
return 'Name should not be empty';
}
},
work: function(){
return this.get('name') + ' is a ' + this.get('designation');
}
});
var PersonCollection = Backbone.Collection.extend({
model: Person
});
var peopleView = Backbone.View.extend({
tagName: 'ul',
render: function(){
//filter through all the items in a collections
//for each, create a new PersonView
//append it to root element
this.collection.each(function(person){
//console.log(person);
var personView = new PersonView({model:person});
this.$el.append(personView.render().el);
},this);
$(this.$el).appendTo('body');
}
});
// The view for a Person
var PersonView = Backbone.View.extend({
tagName : 'li',
className : 'person',
id : 'person-id',
template: _.template( $('#personTemplate').html() ),
initialize : function(){
_.bindAll(this,'render');
//console.log(this.model)
this.render();
},
render: function(){
$(this.$el.html(this.template(this.model.toJSON()))).appendTo('body');
return this;
}
});
var modelperson = new Person;
var viewperson = new PersonView({collection:new PersonCollection(),model : modelperson});
var personCollection = new PersonCollection([
{
name: "raghu",
age:24,
designation: "CEO"
},
{
name: "shashank",
age:23,
designation: "CTO"
},
{
name : "junaid",
age : 30,
designation : "UI"
},
{
name: "vishnu",
age: 23,
designation: "content developer"
}
]);
var pw = new peopleView({collection: personCollection});
pw.render();
html
<script type="text/template" id="personTemplate">
<div class="row">
<div class="cell"><%= name %></div>
<div class="cell"><%= age %></div>
<div class="cell"><%= designation %></div>
</div>
</script>
test on jsfiddle http://jsfiddle.net/HVqMs/1/
You mean fetching the collection? here is a simple example
var Group = Backbone.Model.extend( {
urlRoot: Constants.URL_PREFIX+'/api/v1/group',
});
var GroupCollection = Backbone.Collection.extend( {
url: Constants.URL_PREFIX+'/api/v1/group',
model: Group,
parse(response){
this.metaData = new Backbone.Model(response.meta);
return response.objects;
}
});
then to fetch the collection lets call it MyCollection
var p = new GroupCollection();
p.fetch().done( function() {
p.each(function(item){
console.log(item.get('name'));
});
});
In my example the json returned is something like this
{
metaData:{ size:10,limit:10},
objects:[{name:'pep',id:1},{name:'pep2',id:2}]
}
thats why I override the parse.

After Adding a record it is not displaying the data immediately to the view

When I loads the page , it is getting all the datas and I am displaying the datas. But When I add a record, that is I am submitting the form "addcontact", the datas are creating in the database. But It is not adding into the collection and that this.collection.on('add') is not getting triggered. So, I think the problem was because of this. Can any one tell me that where I am doing wrong? Is there any other way to solve this.
This code works functionally, but the only problem with this is , on creating new record using this.collection.create({new post},{wait: true}); the values are getting updated in the database. But it is not adding into the collection.
(function(){
Backbone.emulateHTTP = true;
//Backbone.emulateJSON = true;
window.App = {
Models : {},
Collections: {},
Views : {},
Router : {}
};
window.vent = _.extend({},Backbone.Events);
window.template = function(id){
return _.template( $('#'+id).html() );
};
// Contact Model
App.Models.Contact = Backbone.Model.extend({
validate: function(attrs) {
if( !attrs.first_name ||
!attrs.last_name ||
!attrs.email_address) {
alert('Fill the missing fields');
}
}
});
// Collection
App.Collections.Contacts = Backbone.Collection.extend({
model: App.Models.Contact,
url : 'index.php/ContactsController/contacts'
});
// Global View
App.Views.App = Backbone.View.extend({
initialize: function(){
vent.on('contact:edit',this.editContact,this);
//console.log(this.collection.toJSON());
App.addContactView = new App.Views.AddContact({collection: App.Contacts});
App.allContactsView = new App.Views.Contacts({collection: App.Contacts});
$('#allcontacts').append(App.allContactsView.el);
}
});
// Add Contact View
App.Views.AddContact = Backbone.View.extend({
el: '#addcontact',
initialize: function(){
this.first_name = $('#first_name');
this.last_name = $('#last_name');
this.email_address = $('#email_address');
this.description = $('#description');
//this will fix it
this.collection.on("change", this.render , this);
},
events: {
'submit' : 'addContact'
},
addContact: function(e){
e.preventDefault();
this.collection.create({
first_name: this.first_name.val(), // <===== same as $this.el.find('#first_name')
last_name: this.last_name.val(),
email_address: this.email_address.val(),
description: this.description.val()
},{wait: true});
this.clearForm();
},
clearForm: function(){
this.first_name.val('');
this.last_name.val('');
this.email_address.val('');
this.description.val('');
}
});
// All Contacts Views
App.Views.Contacts = Backbone.View.extend({
tagName: 'tbody',
initialize: function(){
this.collection.on('add',this.addOne,this);
this.render();
},
render: function(){
this.collection.each(this.addOne,this);
//console.log(this.el);
return this;
},
addOne: function(contact){
var ContactView = new App.Views.Contact({model: contact});
//console.log(ContactView.render().el);
this.$el.append(ContactView.render().el);
}
});
// A view for a single View
App.Views.Contact = Backbone.View.extend({
tagName: 'tr',
template: template('allContactsTemplate'),
initialize: function(){
this.model.on('change',this.render,this);
},
render: function(){
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
})();

Backbone error can not call method 'toJSON'?

I wanna render every waiter from my collection but console still show me error :
Uncaught TypeError: Cannot call method 'toJSON' of undefined
this is my code :
(function() {
window.App = {
Models: {},
Views: {},
Collections: {}
};
window.template = function(id) {
return _.template( $('id' + id).html() );
},
// WAITER MODEL
App.Models.Waiter = Backbone.Model.extend({
defaults: function() {
return {
title: 'Waiter Name',
id: []
};
}
});
// A LIST OF WAITERS COLLECTION
App.Collections.Waiters = Backbone.Collection.extend({
model: App.Models.Waiter
});
// VIEW FOR ALL WAITERS
App.Views.Waiters = Backbone.View.extend({
tagName: 'ul',
render: function() {
this.collection.each(function(waiter) {
var waiterView = new App.Views.Waiter({ model: waiter });
this.$el.append(waiterView.render().el);
}, this);
return this;
}
});
// A VIEW FOR ONE PERSON
App.Views.Waiter = Backbone.View.extend({
tagName: 'li',
template: _.template("<%= title %><%= id %>"),
render: function() {
this.$el.html( this.template(this.model.toJSON()) );
return this;
},
});
waitersCollection = new App.Collections.Waiters([
{
title: 'ferko fristansky',
id: 2
},
{
title: 'ferko bandaska',
id: 3
},
{
title: 'fvwerv fristansky',
id: 4
}
]);
var waitersView = new App.Views.Waiter({ collection: waitersCollection });
$(document.body).append(waitersView.render().el);
})();
You're creating your waiterView with a collection:
var waiterView = new App.Views.Waiter({ collection: waitersCollection });
but App.Views.Waiter is a model-based view; that means that this.model will be undefined inside your App.Views.Waiter and so this will fail:
this.$el.html( this.template(this.model.toJSON()) );
// this is undefined -------------^^^^^
You probably want to create an App.Views.Waiters instead:
var waitersView = new App.Views.Waiters({ collection: waitersCollection });
Then, inside App.Views.Waiters, you'd create one App.Views.Waiter for each model in the collection rather than a new App.Views.extend({ model: waiter }):
render: function() {
this.collection.each(function(waiter) {
var waiterView = new App.Views.Waiter({ model: waiter });
this.$el.append(waiterView.render().el);
}, this);
return this;
}
As an aside, be careful with this:
App.Models.Waiter = Backbone.Model.extend({
defaults: {
title: 'Waiter Name',
id: []
}
});
The values from defaults are shallow copied so everything that uses those defaults will end up using exactly the same id array and that can lead to strange bugs when you have several models sharing the same id array. If you have mutable values in defaults, you usually want to use a function instead so that everyone gets their own distinct values:
App.Models.Waiter = Backbone.Model.extend({
defaults: function() {
return {
title: 'Waiter Name',
id: []
};
}
});

Backbone.js nested model

I am just starting to learn Backbone.js. How do i create a Backbone model for the following use case
States:[0-n]
name
counties:[0-n]
name
cities:[0-n]
name:
parks[0-n]
name:
Any help will is greatly appreciated.
this is what i tries so far
window.Team = Backbone.Model.extend({
initialize: function(){
this.id = this.get('id');
}
});
window.Teams = Backbone.Collection.extend({model:Team});
window.Ward = Backbone.Model.extend({
initialize: function(){
this.name = this.get('name');
this.code = this.get('code');
this.teams = new Teams(this.get('teams'));
}
});
window.Wards = Backbone.Collection.extend({model:Ward});
window.LGA = Backbone.Model.extend({
initialize: function(){
this.name = this.get('name');
this.wards = new Wards(this.get('wards'));
}
});
window.LGAs = Backbone.Collection.extend({model:LGA});
window.State = Backbone.Model.extend({
initialize: function(){
this.name = this.get('name');
this.lgas = new Wards(this.get('lgas'));
}
});
window.States = Backbone.Collection.extend({model:State,
initialize: function(){
_.bindAll(this, 'fetch_success');
this.bind('change', this.fetch_success);
},
url: function(){
return "data.json"
},
fetch_success:function(){
var data = Backbone.Syphon.serialize(someViewWithAForm);
var model = new Backbone.Model(data);
}
});
Thanks
Depending on how you build/fetch your models, you may be interested in Backbone-relational, which lets you define relationships between models.
From the docs:
Person = Backbone.RelationalModel.extend({
relations: [
{
type: 'HasMany',
key: 'jobs',
relatedModel: 'Job',
reverseRelation: {
key: 'person'
}
}
]
});

Categories