1
votes

I have an app which uses both Backbone and Handlebars on a server and a client.

Server

Backbone and Express-Handlebars installed

app.js

app.set('views', path.join(__dirname, '/views'));
app.engine('.hbs', expHbs({
  defaultLayout: 'index', 
  extname: '.hbs'
}));
app.set('view engine', '.hbs');

index.js

exports.init = function(req, res){
  res.render('contact/index');
};

index.hbs

<div class="row">
  <div class="col-sm-6">
    <div class="page-header">
      <h1>Send A Message</h1>
    </div>
    <div id="contact"></div>
  </div>
  ....some code

<script id="tmpl-contact" type="text/x-handlebars-template">
  <form>

      bootstrap with handlebars temlates {{....}} in here

  </form>
</script>

Client

On the client I have Backbone and Handlebars installed via Bower

In index.js Backbone.view

var Handlebars = require('handlebars');
  app.ContactView = Backbone.View.extend({
    el: '#contact',
    template: Handlebars.compile( $('#tmpl-contact').html() ),
    events: {
      'submit form': 'preventSubmit',
      'click .btn-contact': 'contact'
   },
    initialize: function() {
      this.model = new app.Contact();
      this.listenTo(this.model, 'sync', this.render);
      this.render();
    },
    render: function() {
      this.$el.html(this.template( this.model.attributes ));
      this.$el.find('[name="name"]').focus();
   },
    preventSubmit: function(event) {
      event.preventDefault();
   },
    contact: function() {
      this.$el.find('.btn-contact').attr('disabled', true);

      this.model.save({
        name: this.$el.find('[name="name"]').val(),
        email: this.$el.find('[name="email"]').val(),
        message: this.$el.find('[name="message"]').val()
      });
    }
  });

What happens is that index.hbs renders on the server-side normally, but it is not rendering a form inside script; it shows empty <div id="contact"></div> and doesn't shows any errors in console.

As shown here Using Handlebars with Backbone, a way to replace underscore templating with handlebars is simply to replace _.template with Handlebars.compile, but neither of these options works for me. I also tried different type attributes for <script> and it's still not working.

How can I fix this? Any help is appreciated. Thanks.

Added full index.js on client

/* global app:true */

var Handlebars = require('Нandlebars');

(function() {
  'use strict';

  app = app || {};

  app.Contact = Backbone.Model.extend({
    url: '/contact/',
    defaults: {
      success: false,
      errors: [],
      errfor: {},
      name: '',
      email: '',
      message: ''
    }
  });

  app.ContactView = Backbone.View.extend({
    el: '#contact',
    template: Handlebars.compile( $('#tmpl-contact').html() ),
    events: {
      'submit form': 'preventSubmit',
      'click .btn-contact': 'contact'
    },
    initialize: function() {
      this.model = new app.Contact();
      this.listenTo(this.model, 'sync', this.render);
      this.render();
    },
    render: function() {
      this.$el.html(this.template( this.model.attributes ));
      this.$el.find('[name="name"]').focus();
    },
    preventSubmit: function(event) {
      event.preventDefault();
    },
    contact: function() {
      this.$el.find('.btn-contact').attr('disabled', true);

      this.model.save({
        name: this.$el.find('[name="name"]').val(),
        email: this.$el.find('[name="email"]').val(),
        message: this.$el.find('[name="message"]').val()
          });
        }
     });

  $(document).ready(function() {
    app.contactView = new app.ContactView();
  });
}());
1
Where are you initializing the view in index.js...? It is not enough to declare a view. You need to initialize it after the required stuff in loaded. - T J
@T J If you mean using Backbone.Model.Extend, I have it it a same file - ASem
(function() { 'use strict'; app = app || {}; app.Contact = Backbone.Model.extend({ url: '/contact/', defaults: { success: false, errors: [], errfor: {}, name: '', email: '', message: '' } }); it was a github.com/jedireza/drywall with Jade and Underscore templates. I'm trying to change it to Handlebars. - ASem
No, you should have new app.ContactView() somewhere. BTW if you're sharing code blocks please edit and add it in question. They are unreadable in comments - T J
$(document).ready(function() { app.contactView = new app.ContactView(); }); will initialize the view then your app loads. Is the content of index.hbs available at that point? or is it loaded later? - T J

1 Answers

1
votes

So as mentioned here Handlebars.compile must load after handlebars template in <script></script> is loaded into the DOM. In this case when using express-hanldebars, a link to js file must be placed after {{{ body }}} element in the main layout. That is the right answer.

There is one more problem here that app can't be defined globally as it appears in index.js

In order to define app globally you have to move app = app || {}; outside the IIFE scope and put it at the beginning of a file and declare it as a global variable: var app = app || {};. If there are multiple backbone files they should all implement a similar structure in order to app be global.