16

When I create view backbone creates empty div-container if el is not set. Template (this.$el.html(this.template(this.model.toJSON()))) inserted in that div. How to avoid this wrapper? I need clean template without any wrappers so I can insert it anywhere I want? It's not reasonable to call jobView.$e.children() with many elements.

<script id="contactTemplate" type="text/html">
                <div class="job">
                    <h1><%= title %>/<%= type %></h1>
                    <div><%= description %></div>
                </div>     
</script>     

var JobView = Backbone.View.extend({
        template:_.template($("#contactTemplate").html()),

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

var jobView = new JobView({
   model:jobModel
});          

console.log(jobView.el);
Peter O.
  • 32,158
  • 14
  • 82
  • 96
Miron
  • 2,137
  • 3
  • 15
  • 14
  • possible duplicate of [Backbone, not "this.el" wrapping](http://stackoverflow.com/questions/11594961/backbone-not-this-el-wrapping) – nikoshr Nov 28 '12 at 09:43

6 Answers6

15

I think the real answer to this question has not been provided yet, simply remove the div from the template and add the className property to JobView! This will result in the markup you require:

The template:

<script id="contactTemplate" type="text/html">
     <h1><%= title %>/<%= type %></h1>
     <div><%= description %></div>
</script>

The view:

var JobView = Backbone.View.extend({
            className: 'job', // this class will be added to the wrapping div when you render the view

            template:_.template($("#contactTemplate").html()),

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

When you call render you will end up with the desired markup:

<div class="job">
  <h1><%= title %>/<%= type %></h1>
  <div><%= description %></div>
</div>
jcvandan
  • 14,124
  • 18
  • 66
  • 103
  • But what if your template has multiple divs instead of one outer div that contains everything? Something like
    ...
    ...
    . This solution will not work.
    – Vic Jul 31 '13 at 15:20
  • @Vic no it wouldn't but that situation wasn't the question, and even if it was you would just move the inner divs into the template. Backbone views can only have one element so it's a moot point anyway. – jcvandan Jul 31 '13 at 16:17
  • 2
    I hate that Backbone does this. Why have template code in both the template html and the view definition? – Matt Jun 15 '15 at 22:41
  • @Matt me too, I think it is a ridiculous design decision – jcvandan Jun 16 '15 at 07:29
8

I think the best solution to this is:

render: function(){
    var html = this.template(this.model.toJSON()));
    var newElement = $(html)
    this.$el.replaceWith(newElement);
    this.setElement(newElement);
    return this;
}
Vic
  • 8,261
  • 6
  • 42
  • 55
  • `this.$el.replaceWith` is useless in this case and all this can be replace with a one-liner: `this.setElement(this.template(this.model.toJSON())));` – Emile Bergeron May 09 '16 at 15:29
  • But it depends on how the parent view handles the rendering of this view. Your way makes it idempotent, which is great for re-rendering without trashing the view. – Emile Bergeron Jun 30 '16 at 18:06
7

Sorry For a late response and this may have already been solved. I find its best to try and make templates and views as easily as possible. I use Handlebars for my templates.

Each of your templates regardless of use will need to have an HTML element associated with them, so in your view pick whatever elements you want and remove that element from your template, instead of trying to go against the grain. You can then set the attributes in your view to mirror those of your removed template element.

var yourview = Backbone.View.extend({
{
     tagName    : *whatever element ( i.e. section, div etc ),
     attributes : {
           id    : 'your element id'
           class : 'your element class'
           etc
     },
})

Then in your template remove that element this this element will be created nicely without wrapping your template, rather than having to change your render method.

theboy
  • 71
  • 1
  • 1
5

You can render the view into any container you like either specifying the $el property yourself or using the setElement() method before calling render:

var jobView = new JobView({
  model:jobModel
});          

jobView.setElement($('#your_selector_here'));

console.log(jobView.el);

If you look at the docs for View.el you'll see that you can also specify the el property either when writing your view, or by passing a parameter in the constructor.

Tallmaris
  • 7,605
  • 3
  • 28
  • 58
2

I have had this same problem with Backbone. In my opinion it is a design flaw. Here is a Reddit post describing some of the solutions possible: http://www.reddit.com/r/javascript/comments/11pkth/how_do_you_render_your_backbonejs_views/

Here is Jeremy Ashkenas' take on the issue:

| If I want to completely encapsulate the HTML inside of my template, without creating any extra divs, | I must replace this.el. At least as far as I know. Is there any better way to do this?

Give up your desire to do that, and you'll have a much easier time ;) A big part of the point of Backbone always providing a view's element ("el") for you, is that your events are valid at all times -- regardless of whether the view is in the DOM, if the data is ready yet, or if the template is available. It's a more stateless way to declare your mouse and keyboard events, relying less on the required ordering of your rendering. If you really want to replace a view's element, use setElement. But it's not recommended, or necessary.

Community
  • 1
  • 1
therin
  • 1,448
  • 1
  • 15
  • 27
  • 1
    It is not a design flaw but rather a design benefit to have all your views encapsulated into an element. Be aware that you can specify this element to be anything you want, not just a div :) – Tallmaris Nov 28 '12 at 09:51
  • I already have them encapsulated into an element when I'm using a template. Loading a template (of any type, mustache, handlebars.js, whatever) into the view should be second nature for a client-side web application framework. I see Jeremy's point of having events valid at all times, but why can't the template be called when the view is instantiated? – therin Nov 28 '12 at 09:57
  • +1 for therin. This effectively splits your layout code between template HTML and View JavaScript. It's not cool and also annoying when dealing with tables. – Matt Jun 15 '15 at 22:45
0

For better or worse, in Backbone you are expected to use the el provided by the view, and tweak it to your liking with the tagName, className, id and attributes properties of that view.

The obvious problem with this approach is that you mix and match JS and HTML like there is no tomorrow. In my opinion, it is better to keep things nicely separated; the characteristics of the el should be specified along with the template.

In 2014, I have written a drop-in component which aims to solve the issue: Backbone.Declarative.Views. It's probably the most underrated of all my open-source stuff, but it does the job just fine.

How?

Include Backbone.Declarative.Views into your project. Then, put those el-defining properties into data attributes of your template.

<script id="my-template"
        type="text/x-template"

        data-tag-name="p"
        data-id="myContainer"
        data-class-name="someClass orOther">

    <!-- template content here -->

</script>

Now, if your view has a template: "#my-template" property, its el is set up as

<p id="myContainer" class="someClass orOther"></p>

And that's it, really. For the full details, check out the docs.

Why data attributes?

Using data attributes solves the basic problem of keeping the HTML-related stuff in your HTML files and templates, and out of your Javascript. At the same time, this approach is fully compatible with the way things are usually done in Backbone.

Ie, all existing Backbone templates continue to work as they should, just as all those tagName definitions scattered throughout Javascript still get applied as usual. Conflicts with other Backbone extensions and plugins are avoided, and there is no need to change legacy code. That makes Backbone.Declarative.Views safe to include into virtually any project.

hashchange
  • 7,029
  • 1
  • 45
  • 41
  • The `setElement` method enables one to keep all the HTML stuff inside the templates, removing the need for tagName, id, className and your component. There's no need to extend HTML with data attributes. – Emile Bergeron May 09 '16 at 15:41
  • @EmileBergeron The `setElement` method works fine if you have a hard-coded node in the page HTML to refer to. It doesn't work for dynamically generated stuff (think list elements). For that, you'd have to prepare the nodes with additional code, which again would scatter the relevant stuff all over various parts of your JS and HTML. – hashchange Jun 30 '16 at 08:38
  • Actually, you can use setElement to display dynamic list by passing the list item template like `this.setElement(this.template)` instead of `this.$el.html(this.template)` – Emile Bergeron Jun 30 '16 at 13:11
  • See what I mean here: http://stackoverflow.com/a/11598543/1218980 That's exactly how one should render dynamic list items. – Emile Bergeron Jun 30 '16 at 15:16
  • @EmileBergeron Every approach has its trade-offs. That's too much to discuss in a comment, but I have written about the [pros and cons of `setElement()` elsewhere](https://github.com/hashchange/backbone.inline.template#alternative-approach-setelement) a while ago. There are cases when `setElement()` is the better (or only) option, but I'd argue that's the exception, not the rule. – hashchange Aug 16 '16 at 19:51