Backbone - Nested Model with Model Binder - backbone.js

ModelBinder doesn't seem to work together with nested model( backbone-nested project) ..the changes from model don't get propogated to the nested elements.On changing the input value the span value doesn't change...If NestedModel is replace with DeepModel it works. Again the NestedModel also works if the person.name is removed and Model has just one level(lastName and firstName).
<script type='text/coffeescript'>
$ ->
class MyModel extends Backbone.NestedModel
defaults:
person:
name :
firstName: 'Bob'
lastName: 'Sass'
window.model = new MyModel
FormView = Backbone.View.extend
initialize: ->
#modelBinder = new Backbone.ModelBinder();
#modelBinder.bind(#model,#el)
el: '#frm'
view = new FormView model: model
</script>
<body>
<form method="post" action="/test" id='frm'>
<div id="welcome"> Welcome, <span id='person.name.firstName'></span> <span id='person.name.lastName'></span>
<br><br>
Edit your information:
<input type="text" name="person.name.firstName" value="zz"/>
<input type="text" name="person.name.lastName" value="nn"/></div>
</form>

I ran into the very same problem. I found success using Backbone.ModelBinder in conjunction with backbone-associations. It allowed me to use ModelBinder with my nested models and accomplish what you are describing.
I took the example you posted and created a fiddle Using Backbone.ModelBinder with backbone-associations using your example. Check it out and see if it answers your question.
The JavaScript ends up looking like this:
var Name = Backbone.AssociatedModel.extend({
defaults: { 'firstName': '', 'lastName': '' }
});
var Person = Backbone.AssociatedModel.extend({
defaults: { 'name': null },
// create a relation for our nested model
relations: [{
'type': Backbone.One,
'key': 'name',
'relatedModel': Name
}]
});
var MyModel = Backbone.AssociatedModel.extend({
defaults: { 'person': null },
// create a relation for our nested model
relations: [{
'type': Backbone.One,
'key': 'person',
'relatedModel': Person
}]
});
var FormView = Backbone.View.extend({
el: '#frm',
initialize: function() {
this._modelBinder = new Backbone.ModelBinder();
},
render: function() {
var bindingsHash = {
'person.name.firstName': [
{ 'selector': '#firstName' },
{ 'selector': '[name=firstName]' }
],
'person.name.lastName': [
{ 'selector': '#lastName' },
{ 'selector': '[name=lastName]' }
]
};
this._modelBinder.bind(this.model, this.el, bindingsHash);
},
close: function() {
this._modelBinder.unbind();
}
});
// create the model
var modelInfo = new MyModel({
'person': {
'name': {
'firstName': 'Bob',
'lastName': 'Sass'
}
}
});
// create the view and render
var view = new FormView({ model: modelInfo });
view.render();

Related

Can't conditionally render view marionette

I can't figure out how to use a function to decide which child view to render in marionette. It seems like it should be pretty simple based on the documentation here: https://marionettejs.com/docs/master/marionette.collectionview.html#collectionviews-childview
I found that page from the composite view docs which inferred that using a function to define a childView should be the same for collection and composite views https://marionettejs.com/docs/master/marionette.compositeview.html#compositeviews-childview)
However, with the following code I am getting the error message "Uncaught TypeError: view.on is not a function." My code is below:
var Backbone = require('backbone');
var Marionette = require('backbone.marionette');
var ToDoModel = require('./models/todo');
var ToDo = Marionette.LayoutView.extend({
tagName: 'li',
template: require('./templates/todoitem.hbs')
});
var TodoList = Marionette.CompositeView.extend({
el: '#app-hook',
template: require('./templates/todolist.html'),
childView: function(item) {
return ToDo;
},
childViewContainer: 'ul',
ui: {
assignee: '#id_assignee',
form: 'form',
text: '#id_text'
},
triggers: {
'submit #ui.form': 'add:todo:item'
},
collectionEvents: {
add: 'itemAdded'
},
modelEvents: {
invalid: 'itemInvalid'
},
onAddTodoItem: function() {
this.model.set({
assignee: this.ui.assignee.val(),
text: this.ui.text.val()
});
if (this.model.isValid()) {
var items = this.model.pick('assignee', 'text');
this.collection.add(items);
}
},
itemAdded: function() {
this.model.set({
assignee: '',
text: ''
});
this.ui.assignee.val('');
this.ui.text.val('');
},
itemInvalid: function() {
console.log('this item is invalid!')
}
});
var todo = new TodoList({
collection: new Backbone.Collection([
{assignee: 'Scott', text: 'Write a book about Marionette'},
{assignee: 'Andrew', text: 'Do some coding'}
]),
model: new ToDoModel()
});
todo.render();
Why isn't the ToDo view being rendered?
It appears as if you are using an older version of Marionette (LayoutView for example was removed in version 3) and referencing the documentation for the newest version (currently 3.5.1).
In older versions of Marionette, childView as a function is not supported, instead you should use getChildView
So, the relevant portion of your code should look like:
var TodoList = Marionette.CompositeView.extend({
...
getChildView: function(item) {
return ToDo;
},
...
});

Update backbone view partially using timer in collection and get subset of a model

Am trying to update the some of the columns in table view when a timer triggered on collection am getting a subset of (only changed) properties. tried all kinds of codes from google but no luck.
Want to update only age property out of name and designation in view not intended to rerender the entire view.
here is my code
<div class='mainBody'>
<table class="table">
<tr class="success">
<td>Name</td>
<td>Age</td>
<td>Ocupation</td>
</tr>
</table>
</div>
<script id="externalViewTemplate" type="text/template">
<td width='25%'> <%= name %> </td>
<td width='25%' id="age"> <%= age %> </td>
<td width='25%'> <%= occupation %> </td>
</script>
Script:
//Person Model
var PersonModel = Backbone.Model.extend({
_id:"id"
});
//Person collection - People
var PeopleCollection = Backbone.Collection.extend({
model: PersonModel,
view: PeopleView,
initialize: function(){
this.updateAge = setInterval(this.getAge.bind(this), 3000);
},
getAge: function()
{
var per = [{
id:1,
age: 31
},
{
id:2,
age: 32
},
{
id:3,
age: 37
}];
this.reset(per);
}
});
//Person view - Responsible for rendering one person
var PersonView = Backbone.View.extend({
tagName: 'tr',
template: _.template( $('#externalViewTemplate').html() ),
initialize: function() {
this.model.on("change:age", this.update, this);
},
update: function() {
this.el.cells["age"].innerHTML = this.model.get("age");
},
render: function(){
this.$el.append(this.template(this.model.toJSON()));
//console.log(this.$el.find('.edit'));
return this;
}
});
//person view collection - rendering the rows collection
var PeopleView = Backbone.View.extend({
view: PersonView,
initialize: function(options){
this.collection = options.collection,
this.collection.on("reset", this.update, this);
},
render: function(){
this.collection.each(function(person){
var personRow = new PersonView({model: person});
this.$el.append(personRow.render().el);
}, this);
},
update: function(){
this.collection.each(function(person){
//var personRow = new PersonView({model: person});
this.model.age = person.age;
}, this);
}
});
//Try this code
var personCollection = new PeopleCollection([
{
id:1,
name: 'Raju',
age: 31,
occupation: 'Dotnet Programmer'
},
{
id:2,
name: 'Rajesh',
age: 32,
occupation: 'Developer'
},
{
id:3,
name: 'Ramesh',
age: 33,
occupation: 'Designer'
}
]
);
var peopleList = new PeopleView({ el: '.table', collection: personCollection});
peopleList.render();
Want to update only age property out of name and designation in view not intended to rerender the entire view.
According to <tr> - MDN cells property returns an HTMLCollection. Your code for updating a cell should be
update: function() {
this.el.cells.namedItem("age").innerHTML = this.model.get("age");
// or this.el.cells.item(1).innerHTML = this.model.get("age");
// this.el.cells[1].innerHTML = this.model.get("age"); might work
},
Since you most likely has jQuery with backbone, you can just do
this.$el.find(".age").text(this.model.get("age"));
Note that I use className "age" because you shouldn't use id in templates which gets duplicated. You can also use index(), eq() etc to access the cell using jQuery instead of className

Render object type in backbone-forms

I'm trying to render a object type with the backbone-forms tool but I can't get it to work.
I define a model:
Models.ModelType = Backbone.Model.extend({
schema: {
object1: {
type: 'Object',
subSchema: {
option: {
type: 'Checkbox'
},
description: {
type: 'Text'
}
}
},
object2: {
type: 'Checkbox'
}
}
});
And then I call it in my view:
var model = new app.Models.ModelType();
var form = new Backbone.Form({
model: model
}).render();
It doesn't render either object1 and object2. When I comment object1, it does render object2.
In my template I have:
<div data-fields="object1"></div>
<div data-fields="object2"></div>
Edit:
Here is the view:
Views.ModelView = Backbone.View.extend({
initialize: function(){
this.render();
},
render: function () {
var model = new app.Models.ModelType();
var form = new Backbone.Form({
model: model
}).render();
$('#form').html(form.el);
}
});
And then I call it like:
var modelView = new ModelView();
In my routes file

Backbone - Accessing Model defaults through Collection in a View

Ok thaht might not be too clear.
I passed a collection to my view. My collection has a Model, and the Model has defaults in an array. When I log the collection from the View it shows no length. But there are 4 defaults in my model. How can I get the default of my model to my view?
The call to view:
var menuLinks = new App.Collections.MenuLinks ;
var newView = new App.Views.Navbar({ collection: menuLinks }) ;
View:
App.Views.Navbar = Backbone.View.extend({
initialize: function(){
console.log(this.collection) ;
//this.render() ;
}
});
COllection:
App.Collections.MenuLinks = Backbone.Collection.extend({
model: App.Models.MenuLinks
});
Model:
App.Models.MenuLinks = Backbone.Model.extend({
//Default menus
defaults:[
{
name: 'Home',
href: ''
},
{ name: 'Trips',
href: '#trips'
},
{ name: 'Login',
href: '#login'
},
{ name: 'LogoutOhYeah',
href: '#logout'
},
]
});
So... you simply want something like:
var collection = this.options.collection, defaults;
defaults = collection.length && collection.models[0].defaults ||{};

how to listen to array submodel's value change

I have a model define like below.
var A = Backbone.Model.extends({
initialize: function(){
this.on('change:users.name',this.onUserNameChanged)
},
onUserNameChanged: function(){ alert('name changed')
});
var a = new A ({
id:1,
name:'test',
users:[
{
id:2,
name:'u1'
},
{
id:3,
name:'u4'
}
]
})
I want add event on the each user name change in Model define.
I have no idea to do this.It's seems hard to me.
It seems that you can't get change events by manipulating Backbone model array members as is described here:
backbone-js-set-model-array-property
Your best option is to set the whole array and listen to a change:users event or even better - come up with a different model that has a Backbone collection for users that will get the event when manipulated:
var A = Backbone.Model.extend({
initialize: function (options) {
_.extend(this, options);
}
});
var Users = Backbone.Collection.extend({
initialize: function () {
this.on('change:name', function () {
alert('name changed');
});
}
});
var a = new A({
id: 1,
name: 'test',
users: new Users([{
id: 2,
name: 'u1'
}, {
id: 3,
name: 'u4'
}])
});
a.users.get('2').set('name', 'u2');

Resources