1
votes

i am doing a Todo application in backbone. The sample was in Underscore template, but i am trying to use backbone with hogan template. its compiling good, but rendering is not working. It throws error.. Uncaught TypeError: Property 'template' of object [object Object] is not a function

This is my js file:

var app = app || {};

(function ($) {
    'use strict';

    // Todo Item View
    // --------------
    _.templateSettings = {
              evaluate : /\{\[([\s\S]+?)\]\}/g,
              interpolate : /\{\{([\s\S]+?)\}\}/g
            };
    app.TodoView = Backbone.View.extend({
        //... is a list tag.
        tagName:  'li',

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

        // The DOM events specific to an item.
        events: {
            'click .toggle': 'toggleCompleted',
            'dblclick label': 'edit',
            'click .destroy': 'clear',
            'keypress .edit': 'updateOnEnter',
            'keydown .edit': 'revertOnEscape',
            'blur .edit': 'close'
        },

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

        render: function () {
            if (this.model.changed.id !== undefined) {
                return;
            }
            //_.each(myAlbum,function(album) {
            //    this.el.append(template.render(album.toJSON()));
            //});
            console.log(Hogan.compile($('#item-template').html()));

                    this.$el.html(this.template(this.model.toJSON()));
                    // Error in the above line

            this.$el.toggleClass('completed', this.model.get('completed'));
            this.toggleVisible();
            this.$input = this.$('.edit');
            return this;
        },

        toggleVisible: function () {
            this.$el.toggleClass('hidden', this.isHidden());
        },

        isHidden: function () {
            var isCompleted = this.model.get('completed');
            return (// hidden cases only
                (!isCompleted && app.TodoFilter === 'completed') ||
                (isCompleted && app.TodoFilter === 'active')
            );
        },

        toggleCompleted: function () {
            this.model.toggle();
        },

        edit: function () {
            this.$el.addClass('editing');
            this.$input.focus();
        },

        close: function () {
            var value = this.$input.val();
            var trimmedValue = value.trim();

            if (!this.$el.hasClass('editing')) {
                return;
            }

            if (trimmedValue) {
                this.model.save({ title: trimmedValue });

                if (value !== trimmedValue) {
                    this.model.trigger('change');
                }
            } else {
                this.clear();
            }

            this.$el.removeClass('editing');
        },

        updateOnEnter: function (e) {
            if (e.which === ENTER_KEY) {
                this.close();
            }
        },

        revertOnEscape: function (e) {
            if (e.which === ESC_KEY) {
                this.$el.removeClass('editing');
            }
        },

        clear: function () {
            this.model.destroy();
        }
    });
})(jQuery);

This is my Html

<!doctype html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">  
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <title>Backbone.js • TodoMVC</title>
<link rel="stylesheet" href="js/assets/base.css">
</head>
<body>
    <section id="todoapp">
    <header id="header">
      <h1>todos</h1>
      <input id="new-todo" placeholder="What needs to be done?" autofocus>
    </header>
    <section id="main">
      <input id="toggle-all" type="checkbox">
      <label for="toggle-all">Mark all as complete</label>
      <ul id="todo-list"></ul>
    </section>
    <footer id="footer"></footer>
  </section>
  <div id="form-horizontal">
    <p>Double-click to edit a todo</p>
    <p>Written by <a href="https://github.com/addyosmani">Addy Osmani</a></p>
    <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
  </div>

  <script type="text/template" id="item-template">
    <div class="view">
      <input class="toggle" type="checkbox" {{ completed ? 'checked' : '' }}>
      <label>{{ title }}</label>
      <button class="destroy"></button>
    </div>
    <input class="edit" value="{{ title }}">
  </script>

   <script type="text/template" id="stats-template">
    <span id="todo-count"><strong>{{ remaining }}</strong> {{ remaining === 1 ? 'item' : 'items' }} left</span>
    <ul id="filters">
      <li>
        <a class="selected" href="#/">All</a>
      </li>
      <li>
        <a href="#/active">Active</a>
      </li>
      <li>
        <a href="#/completed">Completed</a>
      </li>
    </ul>
    {[ if (completed) { ]}
    <button id="clear-completed">Clear completed ({{ completed }})</button>
    {[ } ]}
  </script>

    <script src="js/lib/base.js"></script>
    <script src="js/lib/hogan-2.0.0.js"></script>
  <script src="js/lib/jquery.js"></script>
  <script src="js/lib/underscore.js"></script>
  <script src="js/lib/backbone.js"></script>
  <script src="js/lib/backbone.localStorage.js"></script>
  <script src="js/models/todo.js"></script>
  <script src="js/collections/todos.js"></script>
  <script src="js/views/todos.js"></script>
  <script src="js/views/app.js"></script>
  <script src="js/routers/router.js"></script>
  <script src="js/app.js"></script>
</body>
</html>

That was awesome .. you made it great. But i have another js file to add a new todo. This also give error. Uncaught TypeError: Cannot call method 'toJSON' of undefined. i am very new to this please help me. My another js file:

var app = app || {};

(function ($) {
    'use strict';



    _.templateSettings = {
              evaluate : /\{\[([\s\S]+?)\]\}/g,
              interpolate : /\{\{([\s\S]+?)\}\}/g
            };
    // The Application
    // ---------------

    // Our overall **AppView** is the top-level piece of UI.
    app.AppView = Backbone.View.extend({

        // Instead of generating a new element, bind to the existing skeleton of
        // the App already present in the HTML.

        el: '#todoapp',

        // Our template for the line of statistics at the bottom of the app.

        //statsTemplate: _.template($('#stats-template').html()),
        statsTemplate:Hogan.compile($('#stats-template').html()),

        // Delegated events for creating new items, and clearing completed ones.
        events: {
            'keypress #new-todo': 'createOnEnter',
            'click #clear-completed': 'clearCompleted',
            'click #toggle-all': 'toggleAllComplete'
        },

        // At initialization we bind to the relevant events on the `Todos`
        // collection, when items are added or changed. Kick things off by
        // loading any preexisting todos that might be saved in *localStorage*.
        initialize: function () {
            this.allCheckbox = this.$('#toggle-all')[0];
            this.$input = this.$('#new-todo');
            this.$footer = this.$('#footer');
            this.$main = this.$('#main');
            this.$list = $('#todo-list');

            this.listenTo(app.todos, 'add', this.addOne);
            this.listenTo(app.todos, 'reset', this.addAll);
            this.listenTo(app.todos, 'change:completed', this.filterOne);
            this.listenTo(app.todos, 'filter', this.filterAll);
            this.listenTo(app.todos, 'all', this.render);

            // Suppresses 'add' events with {reset: true} and prevents the app view
            // from being re-rendered for every model. Only renders when the 'reset'
            // event is triggered at the end of the fetch.
            app.todos.fetch({reset: true});
        },

        // Re-rendering the App just means refreshing the statistics -- the rest
        // of the app doesn't change.
        render: function () {
            var completed = app.todos.completed().length;
            var remaining = app.todos.remaining().length;

            if (app.todos.length) {
                this.$main.show();
                this.$footer.show();

                this.$el.html(this.template.render(this.model.toJSON()));

                this.$footer.html(this.statsTemplate.render({
                    completed: completed,
                    remaining: remaining
                }));

                this.$('#filters li a')
                    .removeClass('selected')
                    .filter('[href="#/' + (app.TodoFilter || '') + '"]')
                    .addClass('selected');
            } else {
                this.$main.hide();
                this.$footer.hide();
            }

            this.allCheckbox.checked = !remaining;
        },

        // Add a single todo item to the list by creating a view for it, and
        // appending its element to the `<ul>`.
        addOne: function (todo) {
            var view = new app.TodoView({ model: todo });
            this.$list.append(view.render().el);
        },

        // Add all items in the **Todos** collection at once.
        addAll: function () {
            this.$list.html('');
            app.todos.each(this.addOne, this);
        },

        filterOne: function (todo) {
            todo.trigger('visible');
        },

        filterAll: function () {
            app.todos.each(this.filterOne, this);
        },

        // Generate the attributes for a new Todo item.
        newAttributes: function () {
            return {
                title: this.$input.val().trim(),
                order: app.todos.nextOrder(),
                completed: false
            };
        },

        // If you hit return in the main input field, create new **Todo** model,
        // persisting it to *localStorage*.
        createOnEnter: function (e) {
            if (e.which !== ENTER_KEY || !this.$input.val().trim()) {
                return;
            }

            app.todos.create(this.newAttributes());
            this.$input.val('');
        },

        // Clear all completed todo items, destroying their models.
        clearCompleted: function () {
            _.invoke(app.todos.completed(), 'destroy');
            return false;
        },

        toggleAllComplete: function () {
            var completed = this.allCheckbox.checked;

            app.todos.each(function (todo) {
                todo.save({
                    'completed': completed
                });
            });
        }
    });
})(jQuery);
1

1 Answers

1
votes

You are using the compiled Hogan template incorrectly.

The result of Hogan.compile() is not a function, but an object. That's why you get the "... template ... is not a function" error. To render the compiled Hogan template, you need to call the "render" function of the object.

So in your case, you need to use

this.template.render(this.model.toJSON())

instead of

this.template(this.model.toJSON())

Visit http://twitter.github.io/hogan.js for more information.