Search code examples
javascriptjqueryhtmlbackbone.jsunderscore.js

Adding views to different el based on condition in Backbone.js


I've got a problem with my Backbone.js app (Fiddle: http://jsfiddle.net/the_archer/bew7x010/3/). The app is list (ol element) with nested lists within. Here's my starting HTML:

<body>
    <ol id="flowList"></ol>
</body>

<script type="text/template" id="item-template">
    <%= content %>
</script>

When the list is empty. I add a new <li> element within it using Backbone.js and focus on the li.

When within the li, if I press the enter key, I want to insert a new li just after the li I pressed enter within.

If I press tab, I want to add a new sub ol element within the list element. I handle the keypress within the li elements like so:

handleKeyboardShortcuts: function(e){
    if (e.keyCode == 13 && !e.shiftKey){
        e.preventDefault();
        this.el = $(e.target).parent();
        Items.create({contnet: "New Item!"});
    }
}

I have a listenTo on my collection, which on add, appends a li to the #flowList ol element using the addOne function:

addOne: function(todo) {
    var view = new ItemView({model: todo});
    $(this.el).append(view.render().el);
}

My problem is:

  1. How do I pass the target element to add to, to the addOne function?
  2. How do I pass an option to the addOne function, which instead of doing $(this.el).append does $(this.el).after ?

For some reason, I can't get my head around how to pass around those details. Here's the full Backbone.js code:

$(function() {
    var Item = Backbone.Model.extend({

    defaults: function() {
      return {
        content: "empty item..."
      };
    }

    });

    var ItemList = Backbone.Collection.extend({
        model: Item,
        localStorage: new Backbone.LocalStorage("todos-backbone"),
    });

    var Items = new ItemList;

    var ItemView = Backbone.View.extend({
        tagName:  "li",

        template: _.template($('#item-template').html()),

        events: {
          "click": "enableEdit",
          "blur": "disableEdit",
        },

        initialize: function() {
          this.listenTo(this.model, 'change', this.render);
          this.listenTo(this.model, 'destroy', this.remove);
        },

        render: function() {
          this.$el.html(this.template(this.model.toJSON()));
          return this;
        },

        enableEdit: function(){
            this.$el.attr("contenteditable","true").focus();
        },

        disableEdit: function(){
            this.$el.attr("contenteditable","false");
        }
    });

   var AppView = Backbone.View.extend({

    el: $("#flowList"),

    events: {
      "keydown li": "handleKeyboardShortcuts"
    },

    initialize: function() {
        this.listenTo(Items, 'add', this.addOne);
        this.listenTo(Items, 'reset', this.addAll);
        this.listenTo(Items, 'all', this.render);

        Items.fetch();

        if (Items.length === 0){
            Items.create({content: "Sample Item!"});
        }
    },

    render: function(e) {
        console.log(e);
    },

    addOne: function(todo) {
      var view = new ItemView({model: todo});
      $(this.el).append(view.render().el);
    },

    addAll: function() {
      Items.each(this.addOne, this);
    },

    handleKeyboardShortcuts: function(e){
        if (e.keyCode == 13 && !e.shiftKey){
            e.preventDefault();
            this.el = $(e.target).parent();
            Items.create({contnet: "New Item!"});
        }
    }
  });

  var App = new AppView;
});

Here's a link to the fiddle: http://jsfiddle.net/the_archer/bew7x010/3/


Solution

  • You could keep track of the selected item, enabling you to insert the new one after or within it.

    In your existing enableEdit handler, trigger an 'item:select' event:

    var ItemView = Backbone.View.extend({
        enableEdit: function(){
            this.trigger('item:select', this);
            this.$el.attr("contenteditable","true").focus();
        }
    });
    

    Then in your AppView, when you add a new item, attach a listener for the new item that updates the value of a 'selectedItem' property.

    Then you can insert the new item into the dom based on the state of that property:

    var AppView = Backbone.View.extend({
        addOne: function(todo) {
            var view = new ItemView({model: todo});
            this.listenTo( view, 'item:select', this.handleItemSelect );
    
            if(this.selectedItem) {
                $(this.selectedItem.el).after(view.render().el);
            }
            else {
                $(this.el).append(view.render().el);
            }
        },
        handleItemSelect: function(item) {
           console.log('item selected:', item);
           this.selectedItem = item;
       }
    });
    

    You should be able to do something similar for the tab key behaviour, by setting a flag when the tab key is pressed, before calling Items.create.