David David - 1 month ago 5
HTML Question

How to update part of a template when a collection is updated?

I'm trying to update only a part of a template used by a Backbone view when a collection is updated. The code below successfully executes the

search()
and
render_list()
methods. Furthermore,
console.log(html)
shows the full template's html. But when I execute the
replaceWith
, it replaces the selector with empty. If I replace
$(selector,html)
with a string (ie: 'test'), it successfully replaces with 'test'.

So, for some reason, the
$(selector, html)
selector isn't doing what it's meant to. What is further weird is that the images within the updated html selector are requested by the browser even though none of the updated html is inserted on into the document.

define([
'jquery',
'underscore',
'backbone',
'collections/tracks',
'collections/genres',
'text!templates/search_view_title.html',
'text!templates/search_view.html'
],function($,_,Backbone,Tracks_collection,Genres_collection,Search_view_title_template,Search_view_template){
var Search_view = Backbone.View.extend({
el: $('#app'),
events: {
'change #genre_select': function(){this.search('genre')},
'click #search_btn': function(){this.search('search')}
},
template: _.template(Search_view_template),
initialize: function(){
// SET SOME IMPORTANT LAYOUT SETTINGS
$('#pagetitle').html(_.template(Search_view_title_template));
$('body').css('padding-top','124px');

this.genre_collection = new Genres_collection();
this.listenTo(this.genre_collection,'update',this.render);
this.genre_collection.fetch();

this.collection = new Tracks_collection();
this.listenTo(this.collection,'update',this.render_list);

},
search: function(searchtype){
switch(searchtype){
case 'genre':
console.log('genre changed');
this.collection.fetch({
data: {limit: 30, type:'genre',genre_id:$('#genre_select').val()}
});
break;
case 'search':
console.log('search changed');
this.collection.fetch({
data: {limit: 30, type:'search',keyword:$('#keyword').val()}
});
break;
}
console.log(this.collection);

},
render_list: function(){
var that = this;
console.log('render list');
var html = that.template({genres: this.genre_collection.models,tracks: this.collection.models});
console.log(html);
var selector = '#tracklist';
console.log($(selector,html));
that.$el.find(selector).replaceWith($(selector,html));
return this;
},
render: function(){
// MAKE 'THIS' ACCESSIBLE
var that = this;
console.log('render');

that.$el.find('#container').html(that.template({genres: this.genre_collection.models}));

return this;
}
});

return Search_view;

});

Answer

Without the HTML templates in hand, I can just assume things.

This is closer to how I would do it:

var Search_view = Backbone.View.extend({
    el: $('#app'),
    events: {
        'change #genre_select': 'onGenreChange',
        'click #search_btn': 'onSearchClick'
    },
    template: _.template(Search_view_template),
    initialize: function() {
        // SET SOME IMPORTANT LAYOUT SETTINGS
        $('#pagetitle').html(Search_view_title_template);

        // Do this in css
        $('body').css('padding-top', '124px');

        this.genre_collection = new Genres_collection();
        this.genre_collection.fetch();

        this.collection = new Tracks_collection();

        this.listenTo(this.genre_collection, 'update', this.render);
        this.listenTo(this.collection, 'update', this.render_list);

    },

    render: function() {
        console.log('render');

        this.$('#container').html(this.template({
            genres: this.genre_collection.models
        }));

        return this;
    },

    render_list: function() {
        console.log('render list');
        var html = this.template({
            genres: this.genre_collection.models,
            tracks: this.collection.models
        });
        console.log(html);
        var $selector = this.$('#tracklist');
        console.log($selector);
        $selector.replaceWith($(html));
        return this;
    },

    ////////////
    // events //
    ////////////
    onSearchClick: function() {
        console.log('search changed');
        this.collection.fetch({
            data: { limit: 30, type: 'search', keyword: $('#keyword').val() }
        });
    },

    onGenreChange: function() {
        console.log('genre changed');
        this.collection.fetch({
            data: { limit: 30, type: 'genre', genre_id: $('#genre_select').val() }
        });
    },
});

$('#pagetitle').html(_.template(Search_view_title_template));

The _.template function returns a function, which itself returns the rendered template when called.


Split your callbacks, functions are cheap and unnecessary switch are ugly.

I made your search into onGenreChange and onSearchClick.


$('body').css('padding-top','124px');

Try to avoid that, it can be easily done with CSS, or even inline <style> tag or inline style="" attribute. If it's necessary for you as it's related to a dynamic behavior, create a class (e.g. search-class) in a css file, then toggle the class with jQuery, moving the "design" responsability out of the js:

$('body').toggleClass('search-class');

var that = this;

This is only necessary when dealing with callbacks where the context (this) is different in the callback.