Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>I don't think I'd create a separate 'TodoSubItem' in this case - why not create a <code>HasMany</code> relation from Todo->Todo, so a Todo can have 0..* <code>children</code>, and 0..1 <code>parent</code>?</p> <p>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 <code>TodoList</code>.</p> <p>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):</p> <pre><code>//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') &amp;&amp; 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 &amp;&amp; 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( '&lt;ul&gt;', { 'class': 'children' } ).append( '&lt;input&gt;', { 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... }); </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload