Search code examples
javascriptbackbone.js

backbone.js click event not firing when new DOM element is added by view


I have written a simple program to create form post. When a new post is created the title is added to document and when clicked on the title, click event works and shows an alert. when i create another post it's title also shows up with title of the first post but, none of them popup an alert when clicked.

 var PostModel = Backbone.Model.extend({
            defaults: {
                "title": "",
                "postTxt":""
            }
        });

        <!-- Collection -->

        var PostCollection = Backbone.Collection.extend({
            url: '/UserPosts',
            model: PostModel
        });

        <!-- Views -->

        var PostModelView = Backbone.View.extend({
            model: new PostModel(),
            tagName: 'li',
            template: _.template($('#post-title').html()),
            initialize: function() {
                console.log('Initialized PostModelView........');
            },
            events: {
               'click a' : 'showAlert'
            },
            showAlert: function(){
                console.log('event');
                alert("You clicked : " + this.model.get('title'));
            },
            render: function() {
                console.log('Running render() of PostModelView.....');
                var htmloutput = this.template(this.model.toJSON());
                this.$el.html(htmloutput);
                return this;
            }
        });

        var postmodelview = new PostModelView();
        var postcollection = new PostCollection();

        var PostCollectionView = Backbone.View.extend({
            collection: postcollection,
            tagName: 'ul',
            initialize: function() {
                console.log('Initialized PostCollectionView .......');
                this.collection.on('add', this.render, this);
                this.collection.on('remove', this.render, this);
                console.log('postcollectionview $el: ' + this.$el.html());
            },

            render: function() {


              this.collection.each(function(postmodel) {
                  console.log(JSON.stringify(postmodel));
                  var postmodelview = new PostModelView({model: postmodel});
                  console.log(JSON.stringify(postmodelview));
                  //var htmlout = postmodelview.render().$el;
                   var htmlout = this.$el.append(postmodelview.render().$el);
                  $('#postcontainer').html('');
                  $('#postcontainer').append(htmlout);
                  //this.$el.append(htmlout);

              },this);

              return this;

            }
        });



        function createPost() {

            var postcollectionview = new PostCollectionView();
            console.log('running creatPost()');
            var postmodel = new PostModel({ title: $('#title').val(), postTxt: $('#postTxt').val() });
            postcollection.create(postmodel);
            console.log("postcollection: " + JSON.stringify(postcollection.toJSON()));
             $('#title').val(''); //clears the input field
             $('#postTxt').val(''); // clears the input field

       }

Solution

  • Let us have a closer look at this:

    var htmlout = this.$el.append(postmodelview.render().$el);
    $('#postcontainer').html('');
    $('#postcontainer').append(htmlout);
    

    append, like most jQuery functions, returns its receiver so htmlout is this.$el. That means that you are repeatedly clearing the content of #postcontainer and putting this.$el back into it.

    What does html('') do? From the fine manual:

    When .html() is used to set an element's content, any content that was in that element is completely replaced by the new content. Additionally, jQuery removes other constructs such as data and event handlers from child elements before replacing those elements with the new content.

    Emphasis mine. So every time you:

    $('#postcontainer').html('');
    

    you're removing all the event handlers from #postcontainer's children, in particular, you're removing all the events from this.$el.

    You shouldn't be doing either of these things:

    $('#postcontainer').html('');
    $('#postcontainer').append(htmlout);
    

    That view should just adding the subview's to its el and let the caller put its el on the page somewhere:

    render: function() {
        this.collection.each(function(postmodel) {
            var postmodelview = new PostModelView({model: postmodel});
            this.$el.append(postmodelview.render().$el);
        }, this);
        return this;
    }
    

    and then in the caller:

    var postcollectionview = new PostCollectionView();
    //...
    #('#postcontainer').append(postcollectionview.render().el);
    

    Various other issues:

    1. Setting the model property on a view's prototype:

      var PostModelView = Backbone.View.extend({
          model: new PostModel(),
      

      doesn't make a lot of sense. Each instance of the view would normally have its own model and that should be supplied when the view is instantiated.

    2. Similarly for setting the collection property on a view:

      var PostCollectionView = Backbone.View.extend({
          collection: postcollection,
      

      You'd normally set collection when you instantiate the view:

      var v = new PostCollectionView({ collection: post collection });
      
    3. You have two different postmodelview variables in different scopes, the top level one is almost certainly a mistake.

    4. You're using labels at the bottom of createPost but labels don't make any sense in that context, I think you mean to say just this:

      $('#title').val('');   // clears the input field
      $('#postTxt').val(''); // clears the input field
      

    Demo: https://jsfiddle.net/ambiguous/tqdf2j0v/