问题
I would like to use backbone-relational to have nested models in my backbone.js application.
I have been able to follow the examples in the documentation to create nested objects (e.g. one-to-many relations). However I don't understand how to bind the lower level elements in a way that will update the upper level objects. I think a working application would be a very helpful tutorial.
So my question is: How do I extend the Todos tutorial using backbone-relational
so that:
- one can add/remove subitems for each item
- double clicking on any subitem edits it (just like the original Todo example)
- clicking on an item hides/reveals its subitems
- subitems are not fetched separately but are simply an array attribute of Todo items
Update: I have created a jsfiddle for this question. So far I have:
- Imported the Todo example mentioned above
- Created a
TodoSubitem
model and aTodoSubitemList
collection - Altered the
Todo
model to extendRelationalModel
instead ofModel
, with aHasMany
relation toTodoSubitem
- Added a
subitem-template
in the html code
But I'm still not sure how to:
- add an input field for
subitems
that appears only when you click aTodo
div - have subitem data as an attribute of
Todo
objects, but still haveTodoSubitemView
bind DOM elements to them (e.g.<li>
tags).
回答1:
I don't think I'd create a separate 'TodoSubItem' in this case - why not create a HasMany
relation from Todo->Todo, so a Todo can have 0..* children
, and 0..1 parent
?
This way, you can re-use the order logic (if you change it to apply per collection), can create deeper nesting levels as desired (or limit that to a certain depth, if you want as well), etc. A number of things will need to be updated though, to accomodate this - for example, keep a list of child views so you can loop over them to mark each as done, and maintaining (and updating from) an ordering per TodoList
.
Anyway, a rough outline of a possible solution to get you started, as a sort of diff with your current version (sorry, it's completely untested and could thus contain horrible mistakes):
//Our basic **Todo** model has `text`, `order`, and `done` attributes.
window.Todo = Backbone.RelationalModel.extend({
relations: [{
type: Backbone.HasMany,
key: 'children',
relatedModel: 'Todo',
collectionType: 'TodoList',
reverseRelation: {
key: 'parent',
includeInJSON: 'id'
}
}],
initialize: function() {
if ( !this.get('order') && this.get( 'parent' ) ) {
this.set( { order: this.get( 'parent' ).nextChildIndex() } );
}
},
// Default attributes for a todo item.
defaults: function() {
return { done: false };
},
// Toggle the `done` state of this todo item.
toggle: function() {
this.save({done: !this.get("done")});
}
nextChildIndex: function() {
var children = this.get( 'children' );
return children && children.length || 0;
}
});
// The DOM element for a todo item...
window.TodoView = Backbone.View.extend({
//... is a list tag.
tagName: "li",
// Cache the template function for a single item.
template: _.template($('#item-template').html()),
// The DOM events specific to an item.
events: {
'click': 'toggleChildren',
'keypress input.add-child': 'addChild',
"click .check" : "toggleDone",
"dblclick div.todo-text" : "edit",
"click span.todo-destroy" : "clear",
"keypress .todo-input" : "updateOnEnter"
},
// The TodoView listens for changes to its model, re-rendering.
initialize: function() {
this.model.bind('change', this.render, this);
this.model.bind('destroy', this.remove, this);
this.model.bind( 'update:children', this.renderChild );
this.model.bind( 'add:children', this.renderChild );
this.el = $( this.el );
this.childViews = {};
},
// Re-render the contents of the todo item.
render: function() {
this.el.html(this.template(this.model.toJSON()));
this.setText();
// Might want to add this to the template of course
this.el.append( '<ul>', { 'class': 'children' } ).append( '<input>', { type: 'text', 'class': 'add-child' } );
_.each( this.get( 'children' ), function( child ) {
this.renderChild( child );
}, this );
return this;
},
addChild: function( text) {
if ( e.keyCode == 13 ) {
var text = this.el.find( 'input.add-child' ).text();
var child = new Todo( { parent: this.model, text: text } );
}
},
renderChild: function( model ) {
var childView = new TodoView( { model: model } );
this.childViews[ model.cid ] = childView;
this.el.find( 'ul.children' ).append( childView.render() );
},
toggleChildren: function() {
$(this.el).find( 'ul.children' ).toggle();
},
// Toggle the `"done"` state of the model.
toggleDone: function() {
this.model.toggle();
_.each( this.childViews, function( child ) {
child.model.toggle();
});
},
clear: function() {
this.model.set( { parent: null } );
this.model.destroy();
}
// And so on...
});
回答2:
I don't think you can make self-relating models in Backbone-relational (as described an the other answer here). When I have tried this, I get an error: Backbone-relational needs the relatedModel to be defined before it can create relationships with it.
So, I've modified the many-to-many pattern described on the backbone-relational page:
https://github.com/PaulUithol/Backbone-relational#many-to-many-relations
In essence, I am creating a linking model to contain references to the model being referred to, so that this link model can be available to Backbone-relational when it is defining the actual model.
I find it convenient to give this link model a separate relationship with both data models in the relationship, so that either can perform look relational look ups. Alternately, you could simply stuff the second model inside the link model, but then the relationship would be one directional unless you explicitly add your own references to the link model in the data model.
Let us create a 'Person' model that has children who are other 'Person' models.
Person = Backbone.RelationalModel.extend({
relations: [
{
type: 'HasMany',
key: 'Children',
relatedModel: 'FamilyRelation',
reverseRelation: {
key: 'Childrenof'
}
},
{
type: 'HasMany',
key: 'Parent',
relatedModel: 'FamilyRelation',
reverseRelation: {
key: 'Parentof'
}
}
]
});
FamilyRelation needs to be defined >before< Person is, so Backbone-relational can create the links, so this goes before the Person model definition in your code:
// FamilyRelation is link model between two "Person"s
// to achieve the Fan/Admiree relation.
FamilyRelation = Backbone.RelationalModel.extend({
})
If we create two "Person"s:
KingKong = new Person({name: 'KingKong'});
SonOfKong = new Person({name: 'SonOfKong'});
Then we can create a FamilyRelationship model that is the 'parentof' SonOfKong, and add it to KingKong's children with this line:
KingKong.get("children").add({"parentof":SonOfKong});
You can then add convenience functions to the Person model, to retrieve the nested models from the FamilyRelationship model, and don't really need to touch FamilyRelation any more, except to make sure it's being saved and retrieved appropriately.
For non-hierarchical relationships (say 'Friend', rather than 'Parent/Child', you still need these two relationships with the linking model in order to be able to retrieve one from the other, which is a bit of a hack, but it works.
回答3:
After some fiddling I have found a way to create a true nested model:
var theModel = Backbone.RelationalModel.extend({ [...] });
theModel.prototype.relations.push({
type: Backbone.HasOne,
key: 'key',
relatedModel: theModel
});
At the point where the model is used (when pushing to the relations on the prototype) it is available, thus making everything work.
回答4:
this post is pretty old by now, but I was searching for the same thing and thought I would share the solution I got.
To create a self-referencing model you simply omit relatedModel
. So something like this:
Person = Backbone.RelationalModel.extend({
relations: [{
type: 'HasMany',
key: 'Children',
}]
})
It is explained in the docs
来源:https://stackoverflow.com/questions/7227597/creating-nested-models-in-backbone-with-backbone-relational