So we are working on a project using marionette and we have made a good progress so far, but we struggling with a part of the marionette nested view model,
so lets assume that we have an apartment (represented as a composite view), and the apartment contains a collection of rooms and a collection of chairs, what we want to do is have the rooms and chairs a direct descending of the aprtment composite view, how can we do this, knowing that the composite view can only have one child collection, should we be using regions?
Have you tried using a Layout instead? it supports regions and an itemview (if needed). The way I am using this is to define several regions in the layout; show a collection view or item view in each region and any other apartment stuff in the layout template. so, for your example, your apartment layout would contain all of the apartment attributes, and a chairs region would contain a chairs collection view, and a rooms region could contain a rooms collection view.
You can do this with nested composite views. For the use case you described you could nest a compositeView for your Apartments and Rooms.
Fiddle:
http://jsfiddle.net/yCD2m/23/
Markup
<div id="apartments"></div>
<script type="text/html" id="appartment">
<div>
<h2>Apartment: <%=apartment%></h2>
<ul></ul>
</div>
</script>
<script type="text/html" id="room">
<h3><%=name%></h3>
<ul></ul>
</script>
<script type="text/html" id="chair">
<b><%=chairType%></b>
</script>
JS
var apartments = [
{apartment: '1a', rooms: [
{name: 'master bed', chairs: []},
{name: 'kitchen', chairs: [
{chairType: 'stool'}, {chairType: 'stool'}]},
{name: 'living room', chairs: [
{chairType: 'sofa'}, {chairType: 'love seat'}]}]
},
{apartment: '2a', rooms: [
{name: 'master bed', chairs: []},
{name: 'kitchen', chairs: [
{chairType: 'shaker'}, {chairType: 'shaker'}]},
{name: 'living room', chairs: [
{chairType: 'sectional'}]}]
}];
var chairModel = Backbone.Model.extend({});
var roomModel = Backbone.Model.extend({
initialize: function(attributes, options) {
this.chairs = new Array();
_.each(attributes.chairs, function(chair){
this.chairs.push(new chairModel(chair));
}, this);
}
});
var ApartmentModel = Backbone.Model.extend({
initialize: function(attributes, options) {
this.rooms = new Array();
_.each(attributes.rooms, function(room){
this.rooms.push(new roomModel(room));
}, this);
}
});
var ApartmentCollection = Backbone.Collection.extend({
model: ApartmentModel
});
var ChairView = Backbone.Marionette.ItemView.extend({
template:'#chair'
});
var RoomView = Backbone.Marionette.CompositeView.extend({
template: '#room',
itemViewContainer: 'ul',
itemView: ChairView,
initialize: function(){
var chairs = this.model.get('chairs');
this.collection = new Backbone.Collection(chairs);
}
});
var ApartmentView = Backbone.Marionette.CompositeView.extend({
template: '#appartment',
itemViewContainer: 'ul',
itemView: RoomView, // Composite View
initialize: function(){
var rooms = this.model.get('rooms');
this.collection = new Backbone.Collection(rooms);
}
});
var ApartmentCollectionView = Backbone.Marionette.CollectionView.extend({
itemView: ApartmentView // Composite View
});
apartmentCollection = new ApartmentCollection(apartments);
apartmentCollectionView = new ApartmentCollectionView({
collection: apartmentCollection
});
App.apartments.show(apartmentCollectionView);
Related
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
My example :
var stooges = [{ name: 'moe', age: 44, userid: 1},
{ name: 'larry', age: 44, userid: 2},
{ name: 'curly', age: 44, userid: 3}];
var StoogeModel = Backbone.Model.extend({});
var StoogeCollection = Backbone.Collection.extend({
model: StoogeModel
});
var StoogeItemView = Backbone.Marionette.ItemView.extend({
tagName: "tr",
template: '#stooge-template'
});
var StoogesCollectionView = Backbone.Marionette.CollectionView.extend({
tagName: "table",
childView: StoogeItemView
});
var myStooges = new StoogeCollection(stooges);
var myStoogesView = new StoogesCollectionView({ collection: myStooges });
myStoogesView.render();
document.body.appendChild(myStoogesView.el);
This example I read in topic backbone.js collection view example using marionette template but I have error:
marionette_backbone.js:1299 Uncaught NoItemViewError: An itemView must be specified
Help me please.
You're using Marionette 1.x as a dependency in your project, but you're attempting to use 2.x interfaces. In 1.x CollectionViews used an "itemView", while 2.x changed the naming to "childView"
Changing your StoogesCollectionView definition to use the itemView naming should fix your issue:
var StoogesCollectionView = Backbone.Marionette.CollectionView.extend({
tagName: "table",
itemView: StoogeItemView
});
Alternatively, you can upgrade Marionette to a newer version.
I'm currently learning Backbone.js and I'm having a hard time learning how to properly use Views (since I have experienced when it comes to MVC), so here is what I'm trying to do:
templates:
<script type="text/template" id="todolist-template">
<ul></ul>
</script>
<script type="text/template" id="todo-template">
<li>
<%= item.name %>
<%= item.description %>
<%= item.priority %>
</li>
</script>
html:
<div id="container"></div>
Views:
var TodoView = Backbone.View.extend({
tagName: 'li',
className: 'todo',
initialize: function() {
this.template = _.template($('#todo-template').html());
this.render();
},
render: function() {
this.$el.html(this.template({item: this.model}));
return this;
}
});
var TodoListView = Backbone.View.extend({
el: '#container',
tagName: 'ul',
className: 'todolist',
initialize: function() {
this.template = _.template($('#todolist-template').html());
this.render();
},
render: function() {
that = this;
this.$el.empty();
this.$el.append(this.template());
this.collection.each(function(model) {
that.$el.append(new TodoView({model: model.toJSON()}));
});
return this;
}
});
Models and Collections:
var Todo = Backbone.Model.extend({
defaults : {
name : '',
priority: '',
description: ''
}
});
var TodoList = Backbone.Collection.extend({
model: Todo
});
var todoList = new app.TodoList([
new Todo({
name: 'unclog the sink',
priority: '10',
description: 'FIX THE SINK!!!'
}),
new Todo({
name: 'get bread',
priority: '0',
description: 'We are out of bread, go get some'
}),
new Todo({
name: 'get milk',
priority: '2',
description: 'We are out of milk, go get some'
})
]);
"misc":
$(function() {
new HeaderView();
new TodoListView({collection: todoList});
router = new AppRouter();
Backbone.history.start();
});
What I'm trying to do is to create a ul which will then get populated with lis that contain the collection's data. I've been trying to fix/debug this code for a while now (at least 3 hours) but I'm constantly hitting errors or wrong results, so please someone explain to me the proper way of implementing this.
edit (resulting HTML):
<div id="container">
<ul></ul>
</div>
At least one problem lies here:
that.$el.append(new TodoView({model: model.toJSON()}));
Should be
that.$el.append(new TodoView({model: model.toJSON()}).render().el);
Since you can't append a view to $el, but rather you should be appending the rendered html
You don't need <li> in your template as your view already wraps the template in those tags. If it still doesn't work, check the DOM and post it here. Same goes for <ul>...
Also, I don't see where you add your ListView to the DOM. render only operates on a local element which isn't part of the DOM yet. Once rendered, you have to add it to the DOM.
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();
I have this collection:
var items = new bb.Collections.QuotesCollection([
{id: 1, name: "item 1", units: []},
{id: 2, name: "item 2", units: []},
{id: 3, name: "item 3", units: []}
]);
And then I output the array "units" like so:
if(this.model.get('units').length){
$(this.el).append('<strong>Units</strong>');
$(this.el).append('<ul>');
for(x in this.model.get('units')){
$(this.el).append('<li class="unit">' + this.model.get('units')[x] + '</li>');
}
$(this.el).append('</ul>');
}
The code above is only POC stuff, so no formal templating as yet.
events: {
"keypress #addUnit" : "addUnit",
"dblclick .unit" : "deleteUnit"
},
deleteUnit: function(){
this.render(); // what do I put here!?
}
What approach do I take to delete an item (the clicked one) from the "units" array?
this is the quick and dirty method:
Assuming the Array's order is not changed through any other medium, you could do
deleteUnit: function() {
// get the index of the li you are clicking
var index = $('.unit').index(this);
this.model.get('units').splice(index, 1);
this.render();
}
This way you have to remember to empty your view element before every render
render: function() {
this.$el.empty();
...
// business as usual
}
First, you probably want to have a view object for each model, so you'd have a collection view which owns the <ul> and looks like this:
var ParentView = Backbone.View.extend({
render: function() {
var html = '<ul></ul>'; // make your html here (usually with templates)
this.$el.append(html);
this.collection.each(_.bind(this.initChild, this));
return this; // so we can chain calls if we want to.
}
initChild: function(model) {
var child = new ChildView({ model: model });
// this.$() is scoped to the view's el property
this.$('ul').append(child.render().el);
}
});
You'd then set up the child views something like this:
var ChildView = Backbone.View.extend({
events: { 'click .delete', 'deleteModel' },
render: function() {
var html = '';// make your html here (usually with templates)
this.$el.append(html);
return this;
},
deleteModel: function(ev){
ev.preventDefault();
// Removes form the collection and sends an xhr DELETE
this.model.destroy();
this.$el.remove();
}
});
The call to Model#destroy will take care of removing it from the collection and sending a DELETE to the server (assuming you have a URL set up in your collection/model).
As far as i understand you need to delete item from model
Person = Backbone.Model.extend({
initialize: function() {
alert("Welcome to this world");
}
});
var person = new Person({ name: "Thomas", age: 67});
delete person.name