Question

I'm implementing view/edit Views in backbone.js for a contacts manager. The web suggested creating a Contact class with child views called ContactView and ContactEdit. The problem is, these need to occupy the same el in the DOM, so I can't nest the children inside the parent. That's because from the outside, I want parent views to only refer to Contact, as if the children were private. I tried this and it works the first time I render():

initialize: function() {
    this.view[0] = new CL.Views.ContactView({model: this.model, el: this.el});
    this.view[1] = new CL.Views.ContactEdit({model: this.model, el: this.el});
},
render: function() {
    this.view[0].render();
    return this;
}

But then I can't swap the views. I tried this.view[0].remove() and everything I can think of, but just can't get the browse and edit views to swap with each other using the same el.

I'm thinking that this means it will be better to have two templates inside one view and just exchange them, which already mostly works. I'm thinking it's backbone.js doesn't handle inheritance well for views at the same level in the DOM.

I'd prefer to avoid backbone.js extensions, but am up for following whatever patterns they implement. I'm trying to do this the "right" way because view/edit is such a common pattern for the forms in our app.

P.S. Another way to state this problem is, how does one hide a view and replace it with another view in backbone.js, if there is no parent view enclosing them?

Thanks in advance for any help you can provide.

No correct solution

OTHER TIPS

I do not quite understand if you want to have a view for a Collection of Models or do you want a view for dealing with a single Model?

If it is a view for a single model,

then you can stick with one view. Just have some events listening for your actions enabling or disabling editing features. (You can do this even by setting up contenteditable="true" on dom elements)

I strongly advise to use some tools like backbone.marionette or chaplinjs. They will save you a ton of time.

Following examples are for Backbone.Marionette

example template

<script type="text/template" id="contact-view-template">
  <span data-bind="firstName"></span>
  <span data-bind="lastName"></span>
  <span data-bind="email"></span>
  <a href="#" data-action="edit">
  <a href="#" data-action="save" style="display:none">
</script>

The view code:

ContactView = Marionette.ItemView.extend({
  template:"#contact-view-template",
  events:{
    "click [data-action=edit]":"edit",
    "click [data-action=save]":"save"
  },
  edit:function(){
    this.$("[data-action=edit]").hide();
    this.$("[data-action=save]").show();
    this.$("[data-bind]").attr("contenteditable",true);
  },
  save:function(){
    var value = {};
    _.each(this.$("[data-bind]",function(el){
      value[el.dataset['bind']]= $(el).val() || $(el).text();
    }));
    this.model.set(value);
    // add your validation here
    this.model.save();
  }
});

If you want to have multiple edit views, than just:

ContactListEditView = Marionette.CompositeView.extend({
  itemView:ContactView.extend({
    tagName:"li"
  }),
  itemViewContainer:"ul",
  template:_.template("<h1>ContactList</h1><p>feel free to edit</p><ul></ul>")
});

If you need 1 edit view and multiple non editable views

(i hope I haven't made any serious errors):

ContactEditView = Marionette.ItemView.extend({
  template:"#contact-edit-view", // your template & bindings
  events:{
    "click [data-action=save]":"save"
  },
  save:function(){
    var value = {};
    _.each(this.$("[data-bind]",function(el){
      value[el.dataset['bind']]= $(el).val() || $(el).text();
    }));
    this.model.set(value);
    this.model.save();
  }
});


ContactListView = Marionette.CompositeView.extend({
  itemView:Marionette.ItemView.extend({
    template:"#contact-view-template",
    events:{
      "click [data-action=edit]":"edit"
    },
    edit:function(){
      this.trigger("edit",this);
    }
  }),
  regions:{
    "edit":"[data-region=edit]"
  },
  initialize:function(){
    this.on("itemview:edit",function(view){
      this.edit.show(new ContactEditView({model:view.model}));
    }.bind(this));
  }
});

I think your problems arise from the fact that your parent view shares the same element with the child views. When you render the ContactView or ContactEdit view, it replaces the DOM element, and when you remove a child view, it (by definition) removes the parent view element as well, since they are the same element.

Instead your should compose the parent view so that the child view is rendered into the container element. Something like

<!-- .contact-container is the parent view el -->
<section class="contact-container">

</section>

And then render the child views into the container:

initialize: function() {
  //don't give the views an element, let them render into the detached
  //element generated by Backbone
  this.view[0] = new CL.Views.ContactView({model: this.model});
  this.view[1] = new CL.Views.ContactEdit({model: this.model});
  this.currentViewIndex = 0;
},
render: function() {
  //replace the contents with the new view
  this.view[this.currentViewIndex].render();
  this.$el.html(this.view.el);

  return this;
},

swap: function() {
  var i = this.currentViewIndex;
  this.view[i].remove();
  //toggle current view between 0 and 1
  this.currentViewIndex = i === 0 ? 1: 0;
  this.render();
}

Then you get

<!-- .contact-container is the parent view el -->
<section class="contact-container">
  <div><!-- your child element --></div>
</section>

If I understand your question correctly, you want a view that inherits from a base View so they can freely operate with the same Model.

/** Declare base View with specific Model. */
var baseView = Backbone.View.extend({
    model: someModel
});

var contactView = baseView.extend({
   initialize: function() {
        /** Do work specific to contact view. */
   }
});

var editContactView = baseView.extend({
   initialize: function() {
       /** Do work specific to contact edit. */
   }
});

var mainView = Backbone.View.extend({
   initialize: function() {
       /** Initialize two contact views */
       new contactView({ });
       new editContactView({ });
   }
});
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top