пятница, 28 июня 2013 г.

Backbone.js

В Backbone имеются только следующие компоненты Model, Collection, View, Router, Namespacing
Однако нет необходимости использовать все их сразу

// Все данные хранятся в моделях, которые объединены коллекции моделей.

// Model

var Photo = Backbone.Model.extend({
    // Default attributes for the photo
    defaults: {
        // Ensure that each photo created has an 'src'
       src: 'placeholder.jpg',
       caption: 'A default image',
       viewed: false
    },
    initialize: function(){}
});

// Collection - group of Models

var PhotoGallery = Backbone.Collection.extend({
    // Reference to this collection's model.
    model: Photo,
    // Filter down the list of all photos that have been viewed
    viewed: function (){
         return this.filter(function(photo){return photo.get('viewed');});
    },
    // Filter down the list to only photos that have not yet been viewed
    unviewed: function(){return this.without.apply(this, this.viewed());}
});

// View следит за изменениями в моделях и перерисовывает себя.

render() отвечает за перерисовку view, используя шаблон underscore.js.
Когда модель меняется, то вызывается метод render().
То есть render() срабатывает только при изменении данных в модели, а не из-зи кликов пользователя по view.
За клики пользователя по view отвечает только controller. И в этом случае контролер уже изменяет содержимое model, а model в свою очередь вызывает метод render для перерисовки view.

var buildPhotoView = function (photoModel, photoContorller) {
    var base = document.createElement('div');
    var photoEl = doucment.createElement('div');

    base.appendChild(photEl);

    var render = function(){
         // We use a templating library such as Undersocre.js
         // templating which generates the HTML for our photo entry
         photoEl.innerHTML = _.template('photoTemplate', {src: photoModel.getSrc()});
    };

    photoModel.addSubscriber(render);

    photoEl.addEventListener('click', function(){
        photoController.handleEvent('click', photoModel);
    }, false);

    var show = function(){
        photoEl.style.display = '';
    };

    var hide = function(){
        photoEl.style.display = 'none';
    };

    return {
        showView: show,
        hideView: hide
    };
};

// Template

<li class="photo">
    <h2><%= caption %></h2>
     <img class="source" src="<%= src %>"/>
     <div class="meta-data"><%= metadata %></div>
</li>

Для использования формата {{ data }} в шаблоне можно испоьзовать следующую установку:

_.templateSettings = { interpolate : /\{\{(.+?)\}\}/g };

// Router - управляет переходами по ссылка в однастраничных приложениях

// Controller - это прослойка между model и view. Controller отвечает за обновление view, когда изменяются данные в model, и за обновление данных в model, когда пользователь кликает по view.

// В Backbone Controller как такового нет, так как View содержат в себе логику Controller, а Routers управляют состояниями приложения.

// Пожтому в качестве контроллера будет использоваться Spine.js

var PhotosController = Spine.Controller.sub({
    init: function(){
        // Устанавливаем прослушку событий 'update' и 'destroy', связывая их с вызовом методов render() и remove()
        this.item.bind('update', this.proxy(this.render));
        this.item.bind('destroy', this.proxy(this.remove));
    },
    render: function(){
        // Handle templating
        this.replace($('#photoTemplate').tmpl(this.item));
        return this;
    },
    remove: function(){
         this.$el.remove();
         this.release();
    }
});

// В Spine controller отвечает за добавление и взаимодействие с DOM events, отрисовку шаблонов и проверку того, что view и model синхронизированы.

// В Backbone.js вся логика крутится вокруг Backbone.View и Backbone.Router. Ранние версии Backbone.js содержали что-то под названием Backbone.Controller, то теперь он переименован в Backbone.Router, чтобы подчеркнуть его роль.

Основное назначение Backbone.Router - это перевод запросов URL в изменение состояние приложения.
Router может содержать традиционные для контроллера команды, такие как связвыание событий между models и view или перерисовка части страницы. Но лучше его так не использовать, оставив только возможность управления URL.

var PhotoRouter = Backbone.Router.extend({
    routes: {'photos/:id' : 'route'},
    route: function(id) {
        var item = photoCollection.get(id);
        var view = new PhotoView({{model: item});
        something.html(view.render().el);
    }
});

// Пример view обладающего возможности controller

var PhotoView = Backbone.View.extend({
    // ... is a list tag.
    tagName: 'li',

    // Pass the content of the photo template through a templating
    // function, cache it for a single photo
    template: _.template($('#photo-template').html()),

    // The DOM events specific to an item.
    events: {'click img': 'toggleView'},

    // The PhotoView listeners for changes to its model, re-rendering.
    initialize: function(){
        _.bindAll(this, 'render');
        this.model.on('change', this.render);
        this.model.on('destroy', this.remove);
    },

    // Re-rendere the photo entry
    render: function() {
        this.$el.html(this.template(this.model.toJSON()));
        return this;
    },

    // Toggle the 'viewed' state of the model.
    toggleViewed: function(){
        this.model.viewed();
    }
});

==============================================================

- Models
- Collections
- Routers
- Views
- Namespacing

==============================================================

Model - хранит в себе данные и логику работы с этими данными.
По сути Model - это мини база данных в браузере.
Сначала вы описываете поля таблицы базы данных и методы взаимодействия с ними, а затем делаете выборку данных или запись новых данных в объект модели.
Модель - это один предмет. Несколько моделей составляют коллекцию.

var Photo = Backbone.Model.extend({
    defaults: {
        src: 'placeholder.jpg',
        tilte: 'an image placeholder',
        coordinates: [0, 0]
    },
    initialize: function(){
        this.on('change:src', function(){
            var src = this.get('src');
            console.log('Image source updated to ' +  src);
        });
    },
    changeSrc: function(source) {
       this.set({src: source});
    }
});

var somePhoto = new Photo({src: 'test.jpg', title: 'testing'});
somePhoto.changeSrc('magic.jpg'); // which triggers 'change:src' and log an update message to the console.

Методы класса Model

// Initialization

Метод initalize() вызывается когда создается новый объект модели с использование ключевого слова new.
Метод initialize() необязателен, но его удобно использовать.

var Photo = Backbone.Model.extend({
    initialize: function(){console.log('this model has been initalized');}
});

var myPhoto = new Photo(); // 'this model has been initalized'

// Getters and Setters

Метод Model.get() позволяет получать значения свойств объекта Model. Значения передаваемые в модель при создании объекта модели можно получать через этот метод.

var myPhoto = new Photo({
                                      title: 'My awesom photo',
                                      src: 'boston.jpg',
                                      location: 'Boston',
                                      tags: ['the big game', 'vacation']
                                     });

var title = myPhoto.get('title'); // 'My awesom photo'
var location =  myPhoto.get('location'); // 'Boston'
var tags = myPhoto.get('tags'); // ['the big game', 'vacation']
var photoSrc = myPhoto.get('src'); // 'boston.jpg'

Для получения значений сразу всех свойств можно использовать attributes

var allValues = myPhoto.attributes;
console.log(allValues);

Однако вместо Model.attributes лучше использовать Model.toJSON()

var allValues = myPhoto.toJSON();
console.log(allValues);

/* this returns {title: "My awesome photo",
                      src:"boston.jpg",
                      location: "Boston",
                      tags:['the big game', 'vacation']} */

Метод set() позволяет устанавливать значения свойств внутри объекта Model.
Лучше избегать использовать установку значений свойств внутри модели напрямую (например myPhoto.title = 'My Moscow';) и устаналивать их через метод set().
Backbone использует метод set() для того чтобы узнавать когда данные в model изменяются и вызывать обновление view.

var Photo = Backbone.Model.extend(
    initialize: function(){console.log('this model has been initialzed');}
);

var myPhoto = new Photo({title: 'My awesom photo', location: 'Boston'}); // 'this model has been initialzed'
var myPhoto2 = new Photo(); // 'this model has been initialzed'

myPhoto.set({title: 'Vacation in Florida', location: 'Florida'});

// Default values

Иногда необходимо устанавливать в базе данных Model значения по умолчанию на тот случай, если пользователь не предоставил свои данные.

Значения по умолчанию для Model определяются в блоке defaults и если при создании объекта модели данные не передаются, то они подставляются изх блока defaults.

var Photo = Backbone.Model.extend({
    initialize: function(){},
    defaults: {
        title: 'Another photo',
        src: 'placeholder.jpg',
        location: 'home',
        tags: ['untagged']
    }
});

var myPhoto = new Photo({
                                      location: 'Boston',
                                      tags: ['the big game', 'vacation']
                                    });

var title = myPhoto.get('title'); // 'Another photo'
var src = myPhoto.get('src'); // 'placeholder.jpg'
var location = myPhoto.get('location'); //  'Boston'
var tags = myPhoto.get('tags'); // ['the big game', 'vacation']

// Listening for changes in model

Любое свойство в объекте модели или все свойства сразу могут прослушиваться для отслеживания изменений их значений.
Listeners могут быть добавлены внутри функции-метода initialize()

initalize: function(){
    this.on('change', function(){console.log('Values for this model have changes');});
}

Если какое-либо свойство модели изменится, то будет выведено сообщение 'Values for this model have changes'

Пример.

var Photo = Backbone.Model.extend({
    initialize: function(){
        console.log('this model has been initialized');
        this.on('change:title', function(){
            var title = this.get('title');
             console.log('My title has been changed to... ' + title);
        });
    },
    defaults: {
          title: 'Another  photo',
          src: 'placeholder.jpg',
          location: 'home',
          tags: ['untagged']
    },
    setTitle: function(newTitle) {
        this.set({title: newTitle});
    }
});

var myPhoto = new Photo({title: 'Fishing at the lake', src: 'fishing.jpg'});
myPhoto.setTitle('Fishing at sea'); // 'My title has been changed to... Fishing at sea'

// Validation

Метод validate() позволяет проверять подставляемые данные в объект модели перед их окончательной вставкой.
Если переданные в объект модели совойства прошли проверку, то они вставляются и метод validate() ничего не возвращает.
Если проверка данных провалилась, то метод validate() возвращает написанное вами значение, означающее ошибку.

var Photo = Backbone.Model.extend({
    initialize: funciton(){
        console.log('this model has been initialized');
        this.on('error', function(model, error){console.log(error);});
    },
    validate: function(attribs) {
        if (attribs.src === undefined) {
            return 'Error! Remember to set a source for your image';
        }
    }
});

var myPhoto = new Photo();
myPhoto.set({title: 'On the beach'}); // 'Error! Remember to set a source for your image'

==============================================================

Views - задают формирование HTML-кода для View и список событий, отвечающих за взаимодействия пользователя с View, такие ка клики, сабмиты и любые другие действия.
Метод формирования HTML-кода View.render() можно связать с событием Model.change(), тем самым позволяя автоматически обновлять view при изменении данных в model.

Creating new views

Для создания нового View необходимо, как обычно, использовать метод extend().

var PhotoSearch = Backbone.View.extend({
    el: $('#results'), // Используем jQuery
    render: function(event){
        var compiled_template = _.template($('#results-template').html());
        this.el.html(compiled_template(this.model.toJSON()));
        return this; // recommended as this enables call to be chained
    },
    events: {
        'submit #searchForm': 'search',
        'click .reset': 'reset',
        'click .advanced': 'switchContext'
    }
    search: function(event) {
        // executed when a form '#searchForm' has been submitted
    },
    reset: function(event) {
        // executed when an element with class 'reset' has been clicked
    },
    switchContext: function(event) {
        // executed when an element with class 'advanced' has been clicked  
    }
});

Все View должны всегда иметь элементы el.

Что такое el?

el - это просто сслыка на какой-либо HTML-элемент DOM.
el позволяет всему содержимому view вставляться в HTML-код DOM только один раз, что позволяет быстрее перерисовывать страницу.

Есть 2 пути связывания el с элементом страницы.
Первый вариант: HTML-элемент уже есть на странице.
Второй вариант: новый HTML-элемент создан для view и вручную добавлен на страницу разработчиком.

Если HTML-элемент уже есть на странице, то вы можете связать его с el через CSS-селектор так

el: '#footer',

или так

el: document.getElementById('footer'),

Если вы хотите создать новый HTML-элемент специально для View, то установите любую комбинацию следующих свойств View:
tagName, id, className. Новый элемент автоматически будет создан для вас фреймворком Backbone.js и ссылка на него автоматически будет добавлена в свойство el.

tagName: 'p', // required, but defaults to 'div' if not set
className: 'container', // optional, you can assign multiple classes to this property like so 'conteiner big bold'
id: 'header', // optional

Выше перечисленный код создаст HTML-элемент, но не добавит его на страницу

<p id="header" class="container"></p>

Understanding render()

render() - это опциональная функция, которая определяет логику отриосвки шаблона. По умолчанию мы используемы шаюлоны из Underscore.js.
Но можно использовать и любые другие шаблонизаторы.
Метода _.template() компилирует шаблон внутри функции, которая может быть выполнена для отрисовки view.

В приведенном выше примере создания View сначала мы компилируем шаблон с id="results-template". Затем устанавливаем el как место для вставки скомпилированного шаблона, куда подставляется данные из нашей модели в формате JSON, связанной с нашим view через скомпилированный шаблон.
Таким образом шаблон с данными из модели вставляется на страницу.

The events attribute

Свойство events в объекте view позволяет связывать события (клики, субмиты), возникающие при взаимодействии пользователя с view, c различными функциями или с самим элементом el, если функции не прописаны.
Запись events имеет следующий формат: {'eventName cssSelector': 'callbackFunction'}.
Поддерживаются следующие типы событий: click, submit, mouseover, dblclick и другие.

==============================================================

Collection - задает набор моделей Model.
Коллекции представляют из себя группу предметов. В то врем, как модель - это один предмет.

var PhotoCollection = Backbone.Collection.extend({
    model: Photo
});

Getters and Setters

Метод get() используется для получения значений отдельного id модели из коллекции.

var skiingEpicness = PhotoCollection.get(2);

Иногда вы захотите получить значение из модеи, используя id пользователя. id пользователя - это свойство, которое Backbone.js автоматически связывает с моделями, которые еще не были сохранены.
Вы можете получит id пользователя из свойства .cid

var mySkiingCrash = PhotoCollection.getByCid(456);

Collection не имеют метода set(), но поддерживают добавление новых моделей через метод add() и удаление старых моделей через метод delete()

var a = new Backbone.Model({title: 'my vacation'});
var b = new Backbone.Model({title: 'my holiday'});

var photoCollection = new PhotoCollection([a, b]);
photoCollection.remove ([a, b]);

Listening for events

Коллекции позволяют устанавливать слушатели событий, которые определяют когда новые модели добавляются в коллекцию или удуляются из коллекции через методы add() и remove()

var PhotoCollection = new Backbone.Collection();

PhotoCollection.on('add', function(photo) {
    console.log('I liked ' + photo.get('title') + ' its this one, right? ' + photo.get('src'));
});

PhototCollection.add([
    {title: 'My trip to Bali', src: 'bali-trip.jpg'},
    {title: 'The flight home', src: 'long-flight-oofta.jpg'},
    {title: 'Uploading fix', src: 'too-many-pics.jpg'}
]);

PhotoCollection.on('change:title', function(){
    console.log('there have been updates made to this collections titles');
});

Fetching models from the server

Метод fetch() получает набор моделей по умолчанию с сервера в формате JSON array.
Когда данные возвращаются сервером, то содержимое текущей коллекции заменяется на содержимое массива.

var PhotoCollection = new BackboneCollection();
PhotoCollection.url = '/photos/';
PhotoCollection.fetch();

В результате этих действий зпускается функция Backbone.sync(), которая вызвается каждый раз, когда Backbone пытается прочитать или сохранить модели на сервер.
Если мы захотим логировать каждый вызов Backbone.sync() мы можем сделать следующее

Backbone.sync = funciton(method, model) {
    console.log('I have been passed ' + method + ' with ' + JSON.stringify(model));
};

Resetting/Refreshing Collections

Вместо того, чтобы добавлять или удалять модели по одиночке, вы можете обновить всю коллекцию за раз.
Метод reset() заменяет содержимое коллекции на новые модели

PhotoCollection.reset([
    {title: 'My trip to Scotland', src: 'scotland-trip.jpg'},
    {title: 'The flight form Scotland', src: 'long-flight: jpg'},
    {title: 'Latest snap of lockness', src: 'lockness.jpg'}
]);

Обратите внимание, что вызов метода Collection.reset() не запускает срабатывания событий add или remove!!!
Вместо них срабатывает собтие reset!!!

==============================================================

Underscore utility funcitons

В состав Backbone входит Underscore.js, методы которого мы можем использовать. Например sortBy()

var sortedByAlphabet = PhotoCollection.sortBy(funciton(photo){
    return photo.get('title').toLowerCase();
});

==============================================================

Routers отвечают за изменение состояние приложения и переход по URL.
Перезод по URL происходит через hash-tag # или через брузерные pushState и History API.
Для одного приложения достаточно использовать один объект Routes

Примеры URL маршрутов.

http://unicorns.com/#whatsup
http://unicorns.com/#search/seasonal-horns/page/2

Формат зщаписи маршрутов
'route': 'mappedFunction'

В данном формате каждый маршрут связан хотя бы с одной вызываемой функцией.

Создадим контроллер, управляющий маршрутами.

var GalleryRouter = Backbone.Route.extend({
    // define the route and funciton maps for this router
    routes: {
        'about': 'showAbout',
        // Sample usage: http://unicorns.com/#about/

        'photos/:id': 'getPhoto',
        // Sample usage: http://unicorns.com/#photos/5/
        // This is an example of using a ':param' variable which allows us to match any of the components between two URL slashes

        'search/:query': 'searchPhotos',
        // Sample usage: http://unicorns.com/#search/lolcats/
        // We can also define multiple routes that are bound to the same map funciton, in this case searchPhotos()

        'search/:query/p:page': 'searchPhotos',
        // Sample usage: http://unicorns.com/#search/lolcats/p1/
        // URL may contain as many ':param' as we wish

        'photos/:id/download/*imagePath': 'downloadPhoto',
        // Sample usage: http://unicorns.com/#photos/5/download/files/lolcats-car.jpg
        // This is an example of using a *splat. splats are able to match any number of URL components and can be combined with ':param'
        // If you wish to use splats for anything beyond default routing, it is probably a good idea to leave them at the end of a URL otherwise you may need to apply regular expression parsing on your fragment

        'other': 'defaultRoute'
        // Sample usage: http://unicorns.com/#anything/
        // This is a default route that also a *splat. Consider the default route a wildcard for URLs that are not matched or where the user has incorrectly typed in a route path manually
    }

    showAbout: function(){}
    getPhoto: function(id) {console.log('You are trying to reach photo ' + id);}, // Note that the id in the above route will be passed to this function
    searchPhotos: function(query, page){
        var page_number = page || 1;
        console.log('Page number:  ' + page_number + ' of the results for ' + query);
    },
    downloadPhoto: function (id, path) {},
    defaultRoute: funciton(other){console.log('Invalid. You attemped to reach: ' + other);}
});

// Поскольку у нас нет route setup по умолчанию,  то необходимо инициализировать Route.

var myGalleryRouter = new GallertRouter();

Далее необходимо инициализировать Backbone.history, поскольку она управляет событиями hashchange в нашем приложении.
Она автоматически будет управлять мершрутами, которые были определены.

Метод Backbone.history.start() сообщает Bakcbone, что все готово для начала отслеиживания маршрутов по изменения hashchange.

Backbone.history.start();
Router.navigate();

Метод navigate() сохраняет состояние приложения при переходе по маршрутам. Он просто обновляет фрагмент URL без запуска события hashchange

Представи что мы определили особый маршрут, когда пользователь изменяет масштаб фото

zoomPhoto: function(factor) {
    this.zoom(factor); // увеличиваем масштаб фото
    this.navigate('zoom/' + factor); // обновляем фрагмент URL, но не запускаем trigger the route
}

Также возможно для Router.navigate() запустить триггер таже как мы обновляем фрагмент URL

zoomPhoto: function(factor) {
    this.zoom(factor); // увеличиваем масштаб фото
   this.navigate('zoom/' + factor, true); // обновляем фрагмент URL и запускаем trigger the route

}

==============================================================

Namespacing - основная идея это избежать возможности засорения глобального пространства имен, во избежания совпадений имен переменных и объектов, для того, чтобы код не ломался.

Варинты создания Namespacing:
- Single global variables
- Object Literals
- Nested namespacing

Single global variable

var myApplication = (function(){
    function Func (){}
    return {
         Func: Func
    };
})();

Пример

var myViews = (funciton(){
    return {
        PhotoView: Backbone.View.extend({}),
        GalleryView: Backbone.View.extend({}),
        AboutView: Backbone.View.extend({})
    };
})();

Проблема Single global variable в том, чтобы убедиться, что никто не использует переменную с таким именем.
Эту проблему можно решить установкой префикса.

var myApplication_photoView = Backbone.View.extend({});
var myApplication_galleryView = Backbone.View.extend({});

Но проблема тут в том, что таки образмо мы создадим множество глобальных объектов.

Object Literals

var myApplication = {};

Данный метод создания переменной не проверяет возможное её существование до создания.

Следующие способы проверяют возможность существования переменной.

if (!myApplication) {var myApplication = {};}

или

var myApplication = myApplication || {};

Пример

var myApplication = {
    models: {},
    collections: {},
    views: {
        pages: {}
    }
};

var myGalleryViews = myGalleryViews || {};
myGalleryViews.photoView = Backbone.View.extend({});
myGalleryViews.galleryView = Backbone.View.extend({});

Nested namespacing

Nested namspacing - это усовершенствование Object Literals

YAHOO.util.Dom.getElementsByClassName('test');

Пример

var galleryApp = gelleryApp || {};

galleryApp.routers = galleryApp.routers || {};
galleryApp.model = galleryApp.model || {};
galleryApp.model.special = galleryApp.model.special || {};

// routers
galleryApp.routers.Workspace = Backbone.Router.extend({});
galleryApp.routers.PhotoSearch = Backbone.Router.extend({});

// models
galleryApp.model.Photo = Backbone.Model.extend({});
galleryApp.model.Comment = Backbone.Model.extend({});

// special models
galleryApp.model.special.Admin = Backbone.Model.extend({});

Предпочтительно использовать nested object namespacing вместе с object literal pattern.

==============================================================

Backbone.events

- on, once, off
- listenTo, listenToOnce, stopListening
- trigger

Используйте проебел для отделения нескольких событий в строке, для которых вызывается данная callbackFunction или записывайте несколько собтий в виде объекта со своими функциями.

this.on('eventName',  callbackFunction, context);

eventName - название события ('click', 'change', 'all')
callbackFunction - функция, вызываемая в момент возникновения события
context - ссылка не объект, который будет представлен как this внутри функции callbackFunction.
              Это необязательный аргумент и если он не прописан, то по умолчанию context будет объект, который вызвал событие, то есть тот к которому привязан метод on.

all - это специальный вид события. Когда функция callbackFunction привязана к событию all, то она будет вызываться для всех триггеров с именем события, в качестве первого аргумент.

Пример.

model.on('all', function(name, a, b, c) {
    console.log(arguments);
});

model.trigger('someevent1', 1, 2, 3); // someevent1, 1, 2, 3
model.trigger('someevent2', 4, 5, 6); // someevent2, 4, 5, 6

all используются скрыто в Collection для проксирования Model events.

Вы можете использовать метод on() для связывания всех событий, но это может привести к утечам памяти.
Главное правило. on() лучше использовать когда необходимо прописать context для callbackFunction.

this.once(eventName, callbackFunction, context);

once() работает также как on(), за исключением того, что событие автоматически удаляется после того, как будет вызвано.
Хороший пример использование once() это вызов осбытия close, поскольку обычно модальный view удаляется из кода страницы после того, как будет закрыт и прослушивание событий для него в дальнейшем уже не требуется.

Пример.

model.once('resolve', function(){
    alert('tears of joy, our issue has been resolved');
});

model.trigger('resolve'); // tears of joy, our issue has been resolved
model.trigger('resolve'); // no alert

this.off(eventName, callbackFunction, context);

off() удаляет привязку события к callbackFunction. Поскольку события вызывают утечку памяти, то их надо удалять после использования.

model.on('eventName', callbackFunction, view);
view.remove(); // Объект удален, а привязка события к функции осталась. Произошла утечка памяти. Используем off() для удаления события
model.off('eventName', callbackFunction, view); // Теперь утечки памяти нет.

Если в off() не передавать никаких аргументов,  то он удалит все события привязанные к данному объекту.
this.off('all', callbackFunction); вызовет тот же эффект -  удалит все события привязанные к данному элементу.

this.listenTo(object, 'eventName', callbackFunction);

listenTo() очень похож на метод on()

Оба метода связывают один объект с другим объектом и функционально делают одно и тоже.
this.on('eventName', callbackFunciton, object);
this.listenTo(object, 'eventName', callbackFunction);

Разница заключается в моменте использования метода off(), для удаления прослушивателей событий.
Используйте метода listenTo() когда context вашей callbackFunction() должене быть объектом, который вы прослушиваете, то есть когда вы хотите чтобы view прослушивал model или collection, но когда view удален прослушки больше не было во избежание утечек памяти.

this.listenToOnce(object, 'eventName', callbackFunction);

listenToOnce() работает также как listenTo(), за исключением того, что событие автоматически удаляется после того, как будет вызвано.

this.stopListening(object, 'eventName', callbackFuntion);

stopListening() позволяет отменить прослушивание сразу всех событий.

Его удобно использовать в таком случае

modelA.on('change', view.renderA, view);
modelB.on('change', view.renderB, view);
modelC.on('change', view.renderC, view);

Теперь мы хотим отменить прослушивание всех этих событий

modelA.off('change', view.renderA, view);
modelB.off('change', view.renderB, view);
modelC.off('change', view.renderC, view);

Не очень удобно. Попробуем сделать тоже самое с методом listenTo()

view.listenTo(modelA, 'change', view.render);
view.listenTo(modelB, 'change', view.render);
view.listenTo(modelC, 'change', view.render);

Теперь мы хотим отменить прослушивание всех этих событий

view.stopListening();

Обычно, когда view удаляется, то Backbone.js автоматически вызывает метод stopListening() для удаления всех событий привязанных к этому view.
В метод stopListening() можно передавать разные аргументы, чтобы удалять только конкретные слушатели событий.

this.trigger('eventName', arguments);
arguments - любой набор аргументов.

trigger() позволяет вызвать вручную любое событие.

Пример.

collection.trigger('sort:before', collection, 'hello');
collection.sort();

Пример перезаписи метода 'sort' для вызова внутри него события 'sort:before'

(function(){
    var sort = Backbone.Collection.prototype.sort;
    Backbone.Collection.prototype.sort = function(options) {
        if (!options.silent) {
            this.trigger('sort:before', this, options);
        }
        return sort.apply(this, arguments);
    };
})();

collection.on('sort:before', beforeCallback);
collection.on('sort', afterCallback);
collection.sort(); // Вызван 'beforeCallback' и затем 'afterCallback'

Расширение событий. Привязывание событий к любым типам объектов.

_.extend()

extend() раширяет любой объект на основе другого бъекта.

var objA = _.extend({
    name: 'Gunner',
    cheer: function(){alert(this.name + ' cheers on objB dancing!');}
}, Backbone.Eventes);

var objB = _.extend({
    dance: function (){this.trigger('dance');}
}, Backbone.Events);

objA.listenTo(objB, 'dance', objA.cheer);
objB.dance();

Составление цепочки вызовов событий.

model.on('change', fucntion(){this.save();});
model.set('name', 'Gunner'); // model saved!

Иногда требуется вызвать callbackFunciton когда вызваны два других события

obj.on('eventA eventB', callbackFunciton);
obj.trigger('eventA'); // вызвана функция callbackFunciton
obj.trigger('eventB'); // вызвана функция callbackFunciton

Для привящки к объекту нескольких событий сразу можно использовать следующую форму записи

obj.on({
    eventA: callbackFunctionA,
    eventB: callbackFuntionB
});

или так

obj.on({
    'eventA eventB': callbackFunciton1,
    'eventC eventD': callbackFuntion2
});

obj.trigger('eventA eventB eventC'); // Это запустит события в следующем порядке: callbackFunciton1, callbackFuntion2, callbackFunciton1, callbackFuntion2.

Еще пример

obj.on({
    'eventA, eventB': callbackFuntion1,
    'eventA eventC': callbackFunction2
});

obj.trigger({
    'eventA': 'hello',
    'eventB eventC': null
});

// Это запустит события в следующем порядке: callbackFuntion1('hello'), callbackFuntion2('hello'), callbackFuntion1(null), callbackFunction2(null).

on() используется когда надо чтобы context функции callbackFunction был объект, который вызывает trigger данное событие event.
listenTo() используется когда надо чтобы context функции callbackFunction был объект, который прослушивает данное событие event.

=============================================

Backbone.view

Во view происходит выбор корневого элемента el, выбор для этого элемента id, class, tag, далее идет инициализация view и отрисовка render, также на view навешиваются события events.

var UserView = Backbone.View.extend({
    properties,
    classProperties
});

var IssueHolderView = Backbone.View.extend({
    className: 'issue-list',
    initialize: function(options) {
        this.options.collection = new app.Issue.Collection();
        this.options.collection.setFilter(app.board.issues, options.testKey, options.testValue);
    },
    template: _.template($('#js-issue-list-template').html()),
    render: function () {
        this.$el.html(this.template({title: this.options.title}));
        this.listView = new app.IssueListView(_.extend({}, this.options, {modelView: app.ItemView, el: this.$('ul')}));
        return this;
    }
});

Creating a view instance

var issueHolder = new IssueHolderView();
issueHolder.render();

Элементы el, id, attributes, className, tagName определяют как View будет вставляться на страницу.

Templating.

Лучше всего помещать код шаблонов внутрь тэгов <script></script>, имеющих атрибут type отличный от text/javascript, чтобы браузер не выполнял их содержимое.
Это позволяет разработчиком быстро определять что-это шабон, а не JavaScript-код.

Пример.

<head>
    <script type="template" class="my-template">
        <%= echo %>
     </script>
</head>

Теперь Мы можем получать код шаблона из HTML-кода страницы по классу "my-template"


var MyView = Backbone.View.extend({
    template: ".my-template", // Мы можем получать код шаблона из HTML-кода страницы

    fetchTemplate: function() {
        return _.template($(this.template).html());
    }
});

Основное назначение Backbone.View - это помощь в работе с событиями DOM.

Все события привязываются к зоне видимости элемента el в шаблоне view.
События могут быть объектом или функцией возвращающей объект с событиями.

Формат событий 'eventType selector': callbackFunction

Пример.

var ModalView = Backbone.View.extend({
    events: {
        // you can use any jQuery selectors here
        'click .modal-mask': 'close',
        'click .modal' : 'stopPropagation',
        // note that an <input> element needs focus for this to work
        'keydown': 'keydown'
    }
});

var WelcomeModalView = ModalView.extend({
    events: function(){
        return _.extend({}, ModalView.prototype.events,{'click .exit': 'close', 'change .js-toggle-show-welcome': 'toggleShowWelcome'});
    }
});

Перерисовку view можно привязать к прослушиванию событий изменения данных в model.

var ItemView = Backbone.View.extend({
    tagName: "li",
    template: _.template($('#js-issue-item-template').html()),
    initialize: function () {
        this.listenTo(this.model.repo, 'change:isActive', this.toggle);
    }
});

Если вы используете привязку событий через listenTo/listenToOnce, то при удалении view эти события будут автоматически удалены.
А если вы привязываете события через on, то вам придется удалять события вручную.
Поэтому лучше во view использовать привязку событий через listenTo/listenToOnce!!!

Удобно будет добавить все сразу удаляющую функцию

Backbone.View.extend({
    remove: function() {
        this.stopListening();
        removeFromSomethingElse(this);
        return Backbone.View.prototype.remove.apply(this, arguments);
    }
});

=============================================

Backbone.Model

- defaults
- initialize
- url
- urlRoot

Поскольку в JavaScript нельзя автоматически определить когда свойство объекта изменяет свое значение, то в Backbone используются методы set() и get() для изменения и получения значений данных, которые вызывают события при изменения данных внутри объекта.
Помимо этого эти методы вызывают валидацию данных перед вставкой их в объект.

model.get('something');

get() возвращает значение свойтва из объекта.

escape() - данный метод экранирует тэги, введенные пользователем во избежание XSS атак.

has - данный метод проверяет имеет ли данная модель заданный ключ (заданное свойство).

test('Test method has', function(){
    var model = new Backbone.Model({ read: false });
    ok(model.has('read'), 'Property exists');
    ok(!model.get('read'), '... but is not truthy');
});

isNew() - данный метода проверяет не содержат ли присланные данные заданное значение id, по которому определяется новизна данных.

toJSON() - данный метод возвращает копию свойств объекта в формате JSON-строки.

set() - даннй метод устанавливает новые значения свойствам модели.

model.set('key', 'value', options);
model.set({key: 'value'}, options);
model.set({key: 'value', key2: 'value2'}, options);

Последний аргумент options необязателен. Если вы не охотите, чтобы внесение изменений вызвало срабатывания, привязаннх к модели событий, то в качестве options напишите {silent: true}

Если вы вместо options напишете {validate: true}, то перед вставкой данные будут проверены и, если валидация не будет выполнена, то метод вернет false и данные в модель не будут вставлены.

unset() - данный метод удаляет ключ, с которым ассоциировано данное значение в модели.

save() - данный метод сохраняет данные в вашей модели. Он использует метод sync() для пересылки данных на сервер.
Метод также позволяет передавать значенияв в формате save({key: value}, options)
В options вы можете поместить callbackFunction.

fetch() - данный метод использует метод synv() для получения данных от сервера. Если модель является частью коллекции, тогда url будет конечной точкой для коллекции.
Если модель не является частью коллекции тогда вы можете определеить свойство urlRoot для модели чтобы определить URL для операций синхронизации с сервером.

destroy() - данный метод удаляет данные на сервера при использовании метода sync() и заголовка HTTP "DELETE".

Пример модели.

var Issue.Model = Backbone.Model.extend({
    defaults: {
        category: 'default'
    },
    initialize: function () {
        this.repo = this.collection.repo;
    },
    url: function () {
        return this.urlRoot() + '/' + this.get('number');
    },
    urlRoot: function () {
        return this.repo.url() + '/issues';
    }
});

Свойства модели

defaults - данное свойство определяет значения по молчанию. Если при создании объекта модели в него не будут переданы аргументы, то значения по умолчанию будут взяты отсюда.
initialize - данный методы вызывается, когда создается новый объект модели.
url и urlRoot - эти методы определяют url по которому будут синхронизироваться данные из этой модели с сервером.

Дополнительные свойства модели.

parse - необязательный метод, который используется для очистки входящих данных от сервера перед их вставкой.

parse: function(response) {
    return response.data;
}

idAttribute - по умолчанию имеет значение 'id'. Оно используется для определения первичного ключа Primary key в вашей базе данных на сервере. Backbone ипользует Primary key для определения того, что сервер прислал новые данные или нет. Если вы используте другой ключ для обозначения Primery key в вашей базе данных на сервере, то можете изменить это значение. Для примера в MongoDB и CouchDB это значение равно '_id'.

validate - необязательный метод, в котором производится проверка данных перед их вставкой в модель.

При создании нового объекта модели в него передаются attributes и options.
attributes - это данные вашей новой модели
options - может содержать:
collection - коллекцию, к которой принадлежит объект вашей модели
parse - true, если вы хотите, чтобы атрибуты были пропущены через метод parse()
silent - true, если вы не хотите, чтобы изменения в модели приводили к запуску всязанных с моделью событий.

Validation - проверка данных
Перед вставкой данные будут проверены и, если проверка провалится, то метод верне сообщение об ошибке или выполнит какое-либо дополнительное действие.

var Repo = Backbone.Model.extend({
    validate: function(attributes, options) {
        if (!attributes.name) { return 'Repo must have a name attribute'; }
    }
});

Собственные методы и свойства модели.

Вы можете добавлять в модели собственные свойтва и методы, которые будут работать с данными в модели.

toBoard: function () {
    var attrs = _.pick(this.attributes, 'id', 'name');
    attrs.owner = {login: this.get('owner').login};
    return attrs;
}

get: function(attr) {
    if (attr === 'full_name') {
        return this.get('owner').login + '/' + this.get('name');
    }
    return Backbone.Model.prototype.get.apply(this, arguments);
}

Также для раширения возможностей моделей вы можете использовать плагины Backbone.Compute и Backbone.Mutators.

=============================================

Backbone.Collection

Если model представляет из себя единичный объект, collection - это набор единичных объектов.
Collection - это упорядоченные наборы моделей.
Используя collection вы можете сохранять, сортировать и манипулировать данными не получая из каждый раз повторно с сервера.

Пример сортировки объектов в collection

users.sortBy(function(user){ return user.get('number_of_posts'); });

После того, как объекты отсортированы view может быть перерисован.

Backbone.Collection наследует многие методы из Underscore.js для группировки, сортировки и фильтрации ваших моделей с данными.

Пример создания Collection

Issue.Collection = Backbone.Collection.extend({
    model: Issue.Model,
    comparator: function (issue) {
       return -1 * Date.parse(issue.get('created_at'));
    },
    url: '/issues'
});

comparator - это функция, котороая запускается каждый раз когда новая модель добавляетс в коллекцию. Она сохраняет правильный порядок элементов в коллекции. В данном случае объекты сортируются по дате добавления. Новешие помещаются первыми.
model - определяет тип моделей, которые будут хранится в данной коллекции. Таким образом мы можем добавлять необработанные данные с сервера сразу в коллекцию, минуя создания нового объекта Model.
url - определяет URL для GET-запроса для получения данных с сервера. Он может быть использован для определения индивидуальных моделей посланных методами GET, POST, PATCH, PUT, DELETE.

Для получения данных с сервера вы можете использовать метод collectio.fetch()
Вы также можете определить метод parse() для извлечения данных из ответа сервера.

Issue.Collection = Backbone.Collection.extend({
    model: Issue.Model,
    url: '/issues',
    parse: function(response){ return response.items; }
});

Также вы можете определить любые другие собственные методы для collection точно также как вы делаете это для Model.

Добавление и удалении моделей из коллекции.

Стоит помнить, что model может состоять из набора collection.
Collection обычно содержит в себе набор отфильтрованных данных (строк) из ващей базы данных на сервере., поэтому добавление и удаление моделей из коллекции не эквивалентно удулению или добавлению их в базе данных.
Обычно вы просто передаете изменения из ваших моделей на сервер.

При добавлени в коллекцию сырые данные могут быть сконвертированы, используя конструктор, определенный в классе модели или через стандартный Backbone.Model.constructor, если он в модели не определен.

Для добавления моделей в коллекцию используется любой из 3-х методов:
add - добавляет модель или массив моделей в коллекцию
push - добавляет модель в конец коллекции
unshift - добавляет модель в начало коллекции

Обновление моделей в коллекции последними данными от сервера осуществаляется любым из этих 2-х методов:
reset -  заменяет все модели в коллекции новыми моделями, старые модели при этом удаляются.
update - аккуратно обновляет коллекцию новыми данными. Новые модели добавляются, старые модели изменяются, а отсутвующие в обновлении модели удаляются.

Если в методе fetch передается аргумент {update:true}, то при обновлении данных в колекции вызвается метода update.

Для переиодического обновления данных в коллекции вы можете вызывать обновление через определенный интервал времени.

setInterval(function(){issue.fetch({update: true})}, 1* 60 * 1000);

Методы удаления моделей из коллекции.
Любой из этих 3-х методов удаляет модель из коллекции, но не уничтожает данные в модели или саму модель.
remove -  удаляет переданные модели из коллекции
pop - удаляет последнюю модель из коллекции и возвращает её
shift - удаляет первую модель из коллекции и возвращает её

Все эти методы запускают событие remove в collection.

Сортировка и фильтрация моделей в коллекции
Методы сортировки и фильтрации моделей в коллекции берутся из Underscrore.js

sortBy - данный метод сортирует модели в коллеции по заданному значению.

issue.sortBy(function(issue) {
    return -1 * issue.get("title").length;
});

where - данный метод отфильтровывает модели в коллекции по заданному значению и возвращает результат.

issues.where({category:"done"});

find -  данный метод возвращает первую модель, прошедшую тест
filter - данный метод возвращает все модели прошедшие тест
reject - данный метод возвращает все модели не прошедшие тест

Группировка коллекций

Иногда необходимо сгруппировать модели в коллекции по какому-либо свойству.
Для группировки используется любой из 2-х методов:
groupBy -  группирует модели в разные массивы базируясь на возвращаемом значении функции-итератора
countBy - группирует также как метод groupBy, но возвращает число моделей в каждой группе, а не массив моделей. Этот метод используется для определения количества моделей в каждой из категорий.

Получение значений из коллекций

Иногда нужно получить набор специфических значений из каждой модели внутри коллекции, а иногда неоюходимо проверить соотвествуют ли данные в коллекции определенному критенрию, чтобы выполнить конкретное действие.
Следующие 3 метода не возвращают данные, а возвращают true или false в зависимости от того прошла ли коллекция тест:
every или all - возвращает true, если все модели прошли тест
some или any - возвращает true, если любая из моделей прошла тест
include или contains - возвращает true, если коллекция содержит переданную модель

Методы возвращающие массив
Следующие 2 метода проходятся по всей коллекции и возвращают нужные значения в виде массива
map - возвращает массив результатов из каждой модели прошедшей через функцию-итератор
pluck - возвращает массив значений, хранящихся в определенном свойстве каждой модели

Pluck - это упрощенная версия map. Map - более мощный метод для получения любых видов значений.

Методы возвращающие одиночные значения

Эти 3 метода удобно использовать, если ваши модели содержат свойства с числовыми значениями:
reduce - пробегается по всем моделям для возвращения одного значения
max - возвращает макисмальное значение из всей коллекции моделей
min - возвращает минимальное значение из всей коллекции моделей

Методы, работающие с каждой моделью в коллекции

Иногда ненужно возвращать какие-либо данные, нужно изменить их. Для этого используются следующие 2 метода:
each -  этот метод просто выполняет функцию для каждой модели в коллекции
invoke - этот метод вызыввает переданные метод для каждой модели в коллекции

События для коллекций
add - слуашет когда модель добавляется в коллекцию
remove -  слушает когда модель удаляется из коллекции
reset -  слушает когда коллекция моделей полностью заменяется на новую
sort -  слушает когда происходит сортировка моделей
destroy -  слушает когда модель в коллекции уничтожается
request - слуашает когда коллекция отправляет запрос на сервер
sync - слушает когда коллекция полностью синхронизируется с сервером

Каждое событие, которое срабатывае на модели срабатывает и на всей коллекции в целом.

Class methods

Для коллекций можно определить методы класса

Repo.Collection = Backbone.Collection.extend(
// instance methods
{

},
// class methods
{
    withOwner: function (login) {
       var repos = new Repo.Collection();
        repos.url = app.apiRoot + '/users/' + login + '/repos';
        return repos;
    }
}
);

=============================================

Backbone.Sync

Методы отправки данных

CREATE → POST
READ → GET
UPDATE → PUT
DELETE → DELETE

Метода sync имеет следующие trigger
request - когда запрос на сервер послан
sync -  когда запрос на сервер выполнен успешно
error - когда при запросе на сервер произошла ошибка

=============================================

Backbone.Router

router - отвечает за навигацию и переключение блоков на странице

=============================================

app.js

// Backbone.Router

var AppRouter = Backbone.Router.extend({
    routes: {
        '': 'list',
        'menu-items/new' : 'itemForm',
        'menu-items/:item' : 'itemDetails'
    },

    list: function () {
        $('#app').html('List screen');
    },

    itemForm: function () {
        $('#app').html('New item form');
    },

    itemDetails: function (item) {
        $('#app').html('Menu item: ' + item);
    }

});

var app = new AppRouter();

$(document).ready(function() {
     Backbone.history.start();
});

// Backbone.View

var MenuItemDetails = Backbone.View.extend({
    template: Handlebars.compile(
        '<div>' +
        '<h1>{{name}}</h1>' +
        '<p><span class="label">{{category}}</span></p>' +
        '<img src="photos/{{imagepath}}" class="img-polaroid" />' +
        '</div>'
    ),

    render: function() {
        this.$el.html(this.template(this.options));
        return this;    
    }
});

var AppRouter = Backbone.Router.extend({
    routes: {
        '': 'list',
        'menu-items/new' : 'itemForm',
        'menu-items/:item' : 'itemDetails'
    },

    list: function () {
        $('#app').html('List screen');
    },

    itemForm: function () {
        $('#app').html('New item form');
    },

    itemDetails: function (item) {
        var view = new MenuItemDetails(
            {
                name: item,
                category: 'Entree',
                imagepath: 'no-image.jpg'
            }
        );

        $('app').html(view.render().el);
    }

});

// Reusing Backbone.View object

var MenuItemDetails = Backbone.View.extend({
    template: Handlebars.compile(
        '<div>' +
        '<h1>{{name}}</h1>' +
        '<p><span class="label">{{category}}</span></p>' +
        '<img src="photos/{{imagepath}}" class="img-polaroid" />' +
        '</div>'
    ),

    render: function() {
        this.$el.html(this.template(this.options));
        return this;    
    }
});

var AppRouter = Backbone.Router.extend({
    routes: {
        '': 'list',
        'menu-items/new' : 'itemForm',
        'menu-items/:item' : 'itemDetails'
    },

    list: function () {
        $('#app').html('List screen');
    },

    itemForm: function () {
        $('#app').html('New item form');
    },

    initialize: function () {
        this.menuItemView = new MenuItemItem(
            {
                 category: 'Entree',
                 imagepath: 'garden-salad.jpg'
            }
        );
    },

    itemDetails: function (item) {
        this.menuItemView.options.name = item;
        $('app').html(this.menuItemView.render().el);
    }

});

// Menu Backbone.View

var MenuView = Backbone.View.extend({
    template: Handlebars.compile(
        '<ul>' +
        '{{#each items}}' +
        '<li>{{this}}</li>' +
        '{{/each}}' +
        '</ul>'
    ),

    render: function(){
        this.$el.html(this.template(this.options));
        return this;
    }
});


var MenuItemDetails = Backbone.View.extend({
    template: Handlebars.compile(
        '<div>' +
        '<h1>{{name}}</h1>' +
        '<p><span class="label">{{category}}</span></p>' +
        '<img src="photos/{{imagepath}}" class="img-polaroid" />' +
        '</div>'
    ),

    render: function() {
        this.$el.html(this.template(this.options));
        return this;    
    }
});

var AppRouter = Backbone.Router.extend({
    routes: {
        '': 'list',
        'menu-items/new' : 'itemForm',
        'menu-items/:item' : 'itemDetails'
    },

    list: function () {
        $('#app').html(this.menuView.render().el);
    },

    itemForm: function () {
        $('#app').html('New item form');
    },

    initialize: function () {
        this.menuView = new MenuView(
            {
                 items: [
                     'Garden salad',
                     'Pizza',
                     'Cheesecake'
                 ]
            }
        );

        this.menuItemView = new MenuItemItem(
            {
                 category: 'Entree',
                 imagepath: 'garden-salad.jpg'
            }
        );
    },

    itemDetails: function (item) {
        this.menuItemView.options.name = item;
        $('app').html(this.menuItemView.render().el);
    }

});

// Extend routes

var MenuCategoryView = Backbone.View.extend({
    template: Handlebars.compile(
        '<h1>{{category}}</h1>' +
        '{{#each images}}' +
        '<img src="photots/{{this}}" class="img-polaroid" />' +
        '{{/each}}'
     ),

    render: function(){
        this.$el.html(this.template(this.options));
        return this;
    }
});

var MenuView = Backbone.View.extend({
    template: Handlebars.compile(
        '<ul>' +
        '{{#each items}}' +
        '<li>{{this}}</li>' +
        '{{/each}}' +
        '</ul>'
    ),

    render: function(){
        this.$el.html(this.template(this.options));
        return this;
    }
});


var MenuItemDetails = Backbone.View.extend({
    template: Handlebars.compile(
        '<div>' +
        '<h1>{{name}}</h1>' +
        '<p><span class="label">{{category}}</span></p>' +
        '<img src="photos/{{imagepath}}" class="img-polaroid" />' +
        '</div>'
    ),

    render: function() {
        this.$el.html(this.template(this.options));
        return this;    
    }
});

var AppRouter = Backbone.Router.extend({
    routes: {
        '': 'list',
        'menu-items/new' : 'itemForm',
        'menu-items/:item' : 'itemDetails',
        'categories/:category' : 'categoryDetails'
    },

    list: function () {
        $('#app').html(this.menuView.render().el);
    },

    itemForm: function () {
        $('#app').html('New item form');
    },

    initialize: function () {
        this.menuCategoryView = new MenuCategoryView(
            {
                 category: 'Entree',
                 images: [
                     'carrots.jpg',
                     'green-beans.jpg',
                     'mashed-potatoes.jpg'
                 ]
            }
        );


        this.menuView = new MenuView(
            {
                 items: [
                     'Garden salad',
                     'Pizza',
                     'Cheesecake'
                 ]
            }
        );

        this.menuItemView = new MenuItemItem(
            {
                 category: 'Entree',
                 imagepath: 'garden-salad.jpg'
            }
        );
    },

    itemDetails: function (items) {
        this.menuItemView.options.name = item;
        $('app').html(this.menuItemView.render().el);
    },

    categoryDetails: funciton (category) {
        this.menuCategoryView.options.category = category;
        $('#app').html(this.menuCategoryView.render().el);
    }  

});

// Backbone.Model

var MenuItem = Backbone.Model.extend({
    defaults: {
        category: 'Entree',
        imagepath: 'no-image',
        name: ''
    }
});

var MenuCategoryView = Backbone.View.extend({
    template: Handlebars.compile(
        '<h1>{{category}}</h1>' +
        '{{#each images}}' +
        '<img src="photots/{{this}}" class="img-polaroid" />' +
        '{{/each}}'
     ),

    render: function(){
        this.$el.html(this.template(this.options));
        return this;
    }
});

var MenuView = Backbone.View.extend({
    template: Handlebars.compile(
        '<ul>' +
        '{{#each items}}' +
        '<li>{{this}}</li>' +
        '{{/each}}' +
        '</ul>'
    ),

    render: function(){
        this.$el.html(this.template(this.options));
        return this;
    }
});


var MenuItemDetails = Backbone.View.extend({
    template: Handlebars.compile(
        '<div>' +
        '<h1>{{name}}</h1>' +
        '<p><span class="label">{{category}}</span></p>' +
        '<img src="photos/{{imagepath}}" class="img-polaroid" />' +
        '</div>'
    ),

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

var AppRouter = Backbone.Router.extend({
    routes: {
        '': 'list',
        'menu-items/new' : 'itemForm',
        'menu-items/:item' : 'itemDetails',
        'categories/:category' : 'categoryDetails'
    },

    list: function () {
        $('#app').html(this.menuView.render().el);
    },

    itemForm: function () {
        $('#app').html('New item form');
    },

    initialize: function () {
        this.menuCategoryView = new MenuCategoryView(
            {
                 category: 'Entree',
                 images: [
                     'carrots.jpg',
                     'green-beans.jpg',
                     'mashed-potatoes.jpg'
                 ]
            }
        );


        this.menuView = new MenuView(
            {
                 items: [
                     'Garden salad',
                     'Pizza',
                     'Cheesecake'
                 ]
            }
        );

        this.menuItemModel = new MenuItem();
        this.menuItemView = new MenuItemView(
            {
                 model: this.menuItemModel
            }
        );
    },

    itemDetails: function (items) {
        this.menuItemModel.set('name', item);
        $('app').html(this.menuItemView.render().el);
    },

    categoryDetails: funciton (category) {
        this.menuCategoryView.options.category = category;
        $('#app').html(this.menuCategoryView.render().el);
    }  

});

// Backbone.View events

var MenuItem = Backbone.Model.extend({
    defaults: {
        category: 'Entree',
        imagepath: 'no-image',
        name: ''
    }
});

var MenuCategoryView = Backbone.View.extend({
    template: Handlebars.compile(
        '<h1>{{category}}</h1>' +
        '{{#each images}}' +
        '<img src="photots/{{this}}" class="img-polaroid" />' +
        '{{/each}}'
     ),

    render: function(){
        this.$el.html(this.template(this.options));
        return this;
    }
});

var MenuView = Backbone.View.extend({
    template: Handlebars.compile(
        '<ul>' +
        '{{#each items}}' +
        '<li>{{this}}</li>' +
        '{{/each}}' +
        '</ul>'
    ),

    render: function(){
        this.$el.html(this.template(this.options));
        return this;
    }
});


var MenuItemDetails = Backbone.View.extend({
    template: Handlebars.compile(
        '<div>' +
        '<h1>{{name}}</h1>' +
        '<p><span class="label">{{category}}</span></p>' +
        '<img src="photos/{{imagepath}}" class="img-polaroid" />' +
        '</div>'
    ),

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

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

var AppRouter = Backbone.Router.extend({
    routes: {
        '': 'list',
        'menu-items/new' : 'itemForm',
        'menu-items/:item' : 'itemDetails',
        'categories/:category' : 'categoryDetails'
    },

    list: function () {
        $('#app').html(this.menuView.render().el);
    },

    itemForm: function () {
        $('#app').html('New item form');
    },

    initialize: function () {
        this.menuCategoryView = new MenuCategoryView(
            {
                 category: 'Entree',
                 images: [
                     'carrots.jpg',
                     'green-beans.jpg',
                     'mashed-potatoes.jpg'
                 ]
            }
        );


        this.menuView = new MenuView(
            {
                 items: [
                     'Garden salad',
                     'Pizza',
                     'Cheesecake'
                 ]
            }
        );

        this.menuItemModel = new MenuItem();
        this.menuItemView = new MenuItemView(
            {
                 model: this.menuItemModel
            }
        );
    },

    itemDetails: function (items) {
        this.menuItemModel.set('name', item);
        $('app').html(this.menuItemView.render().el);
    },

    categoryDetails: funciton (category) {
        this.menuCategoryView.options.category = category;
        $('#app').html(this.menuCategoryView.render().el);
    }  

});

// Backbone.Model data loading

var MenuItem = Backbone.Model.extend({
    defaults: {
        category: 'Entree',
        imagepath: 'no-image',
        name: ''
    },
    urlRoot: '/items'
});

var MenuCategoryView = Backbone.View.extend({
    template: Handlebars.compile(
        '<h1>{{category}}</h1>' +
        '{{#each images}}' +
        '<img src="photots/{{this}}" class="img-polaroid" />' +
        '{{/each}}'
     ),

    render: function(){
        this.$el.html(this.template(this.options));
        return this;
    }
});

var MenuView = Backbone.View.extend({
    template: Handlebars.compile(
        '<ul>' +
        '{{#each items}}' +
        '<li>{{this}}</li>' +
        '{{/each}}' +
        '</ul>'
    ),

    render: function(){
        this.$el.html(this.template(this.options));
        return this;
    }
});


var MenuItemDetails = Backbone.View.extend({
    template: Handlebars.compile(
        '<div>' +
        '<h1>{{name}}</h1>' +
        '<p><span class="label">{{category}}</span></p>' +
        '<img src="photos/{{imagepath}}" class="img-polaroid" />' +
        '</div>'
    ),

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

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

var AppRouter = Backbone.Router.extend({
    routes: {
        '': 'list',
        'menu-items/new' : 'itemForm',
        'menu-items/:item' : 'itemDetails',
        'categories/:category' : 'categoryDetails'
    },

    list: function () {
        $('#app').html(this.menuView.render().el);
    },

    itemForm: function () {
        $('#app').html('New item form');
    },

    initialize: function () {
        this.menuCategoryView = new MenuCategoryView(
            {
                 category: 'Entree',
                 images: [
                     'carrots.jpg',
                     'green-beans.jpg',
                     'mashed-potatoes.jpg'
                 ]
            }
        );


        this.menuView = new MenuView(
            {
                 items: [
                     'Garden salad',
                     'Pizza',
                     'Cheesecake'
                 ]
            }
        );

        this.menuItemModel = new MenuItem();
        this.menuItemView = new MenuItemView(
            {
                 model: this.menuItemModel
            }
        );
    },

    itemDetails: function (items) {
        this.menuItemModel.set('id', item);
        this.menuItemModel.fetch();
        $('app').html(this.menuItemView.render().el);
    },

    categoryDetails: funciton (category) {
        this.menuCategoryView.options.category = category;
        $('#app').html(this.menuCategoryView.render().el);
    }  

});

// Backbone.Model for list screen

var MenuList = Backbone.Model.extend({
    defauts: {
        items: [
            'Garden Salad',
            'Pizza',
            'Cheesecake'
        ]
    }
});

var MenuItem = Backbone.Model.extend({
    defaults: {
        category: 'Entree',
        imagepath: 'no-image',
        name: ''
    },
    urlRoot: '/items'
});

var MenuCategoryView = Backbone.View.extend({
    template: Handlebars.compile(
        '<h1>{{category}}</h1>' +
        '{{#each images}}' +
        '<img src="photots/{{this}}" class="img-polaroid" />' +
        '{{/each}}'
     ),

    render: function(){
        this.$el.html(this.template(this.options));
        return this;
    }
});

var MenuView = Backbone.View.extend({
    template: Handlebars.compile(
        '<ul>' +
        '{{#each items}}' +
        '<li>{{this}}</li>' +
        '{{/each}}' +
        '</ul>'
    ),

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

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


var MenuItemDetails = Backbone.View.extend({
    template: Handlebars.compile(
        '<div>' +
        '<h1>{{name}}</h1>' +
        '<p><span class="label">{{category}}</span></p>' +
        '<img src="photos/{{imagepath}}" class="img-polaroid" />' +
        '</div>'
    ),

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

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

var AppRouter = Backbone.Router.extend({
    routes: {
        '': 'list',
        'menu-items/new' : 'itemForm',
        'menu-items/:item' : 'itemDetails',
        'categories/:category' : 'categoryDetails'
    },

    list: function () {
        $('#app').html(this.menuView.render().el);
    },

    itemForm: function () {
        $('#app').html('New item form');
    },

    initialize: function () {
        this.menuCategoryView = new MenuCategoryView(
            {
                 category: 'Entree',
                 images: [
                     'carrots.jpg',
                     'green-beans.jpg',
                     'mashed-potatoes.jpg'
                 ]
            }
        );

        this.menuList = new MenuList();
        this.menuView = new MenuView(
            {
                model: this.menuList
            }
        );

        this.menuItemModel = new MenuItem();
        this.menuItemView = new MenuItemView(
            {
                 model: this.menuItemModel
            }
        );
    },

    itemDetails: function (items) {
        this.menuItemModel.set('id', item);
        this.menuItemModel.fetch();
        $('app').html(this.menuItemView.render().el);
    },

    categoryDetails: funciton (category) {
        this.menuCategoryView.options.category = category;
        $('#app').html(this.menuCategoryView.render().el);
    }  

});

// Backbone.Collection

var MenuList = Backbone.Model.extend({
    defauts: {
        items: [
            'Garden Salad',
            'Pizza',
            'Cheesecake'
        ]
    }
});

var MenuItem = Backbone.Model.extend({
    defaults: {
        category: 'Entree',
        imagepath: 'no-image',
        name: ''
    },
    urlRoot: '/items'
});

var MenuItems = Backbone.Collection.extend({
    model: MenuItem,
    url: '/items'
});

var MenuCategoryView = Backbone.View.extend({
    template: Handlebars.compile(
        '<h1>{{category}}</h1>' +
        '{{#each images}}' +
        '<img src="photots/{{this}}" class="img-polaroid" />' +
        '{{/each}}'
     ),

    render: function(){
        this.$el.html(this.template(this.options));
        return this;
    }
});

var MenuView = Backbone.View.extend({
    template: Handlebars.compile(
        '<ul>' +
        '{{#each items}}' +
        '<li>{{this}}</li>' +
        '{{/each}}' +
        '</ul>'
    ),

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

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


var MenuItemDetails = Backbone.View.extend({
    template: Handlebars.compile(
        '<div>' +
        '<h1>{{name}}</h1>' +
        '<p><span class="label">{{category}}</span></p>' +
        '<img src="photos/{{imagepath}}" class="img-polaroid" />' +
        '</div>'
    ),

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

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

var AppRouter = Backbone.Router.extend({
    routes: {
        '': 'list',
        'menu-items/new' : 'itemForm',
        'menu-items/:item' : 'itemDetails',
        'categories/:category' : 'categoryDetails'
    },

    list: function () {
        $('#app').html(this.menuView.render().el);
    },

    itemForm: function () {
        $('#app').html('New item form');
    },

    initialize: function () {
        this.menuCategoryView = new MenuCategoryView(
            {
                 category: 'Entree',
                 images: [
                     'carrots.jpg',
                     'green-beans.jpg',
                     'mashed-potatoes.jpg'
                 ]
            }
        );

        this.menuList = new MenuList();
        this.menuView = new MenuView(
            {
                model: this.menuList
            }
        );

        this.menuItemModel = new MenuItem();
        this.menuItemView = new MenuItemView(
            {
                 model: this.menuItemModel
            }
        );

        this.menuItems = new MenuItems();
        this.menuItems.fetch();
    },

    itemDetails: function (items) {
        this.menuItemModel.set('id', item);
        this.menuItemModel.fetch();
        $('app').html(this.menuItemView.render().el);
    },

    categoryDetails: funciton (category) {
        this.menuCategoryView.options.category = category;
        $('#app').html(this.menuCategoryView.render().el);
    }  

});

// Rendering Backbone.Collection in View

var MenuList = Backbone.Model.extend({
    defauts: {
        items: [
            'Garden Salad',
            'Pizza',
            'Cheesecake'
        ]
    }
});

var MenuItem = Backbone.Model.extend({
    defaults: {
        category: 'Entree',
        imagepath: 'no-image',
        name: ''
    },
    urlRoot: '/items'
});

var MenuItems = Backbone.Collection.extend({
    model: MenuItem,
    url: '/items'
});

var MenuCategoryView = Backbone.View.extend({
    template: Handlebars.compile(
        '<h1>{{category}}</h1>' +
        '{{#each images}}' +
        '<img src="photots/{{this}}" class="img-polaroid" />' +
        '{{/each}}'
     ),

    render: function(){
        this.$el.html(this.template(this.options));
        return this;
    }
});

var MenuView = Backbone.View.extend({
    template: Handlebars.compile(
        '<ul>' +
        '{{#each models}}' +
        '<li>{{attributes.name}}</li>' +
        '{{/each}}' +
        '</ul>'
    ),

    initialize: function() {
        this.listenTo(this.collection, 'reset', this.render);
    },

    render: function(){
        this.$el.html(this.template(this.collection));
        return this;
    }
});


var MenuItemDetails = Backbone.View.extend({
    template: Handlebars.compile(
        '<div>' +
        '<h1>{{name}}</h1>' +
        '<p><span class="label">{{category}}</span></p>' +
        '<img src="photos/{{imagepath}}" class="img-polaroid" />' +
        '</div>'
    ),

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

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

var AppRouter = Backbone.Router.extend({
    routes: {
        '': 'list',
        'menu-items/new' : 'itemForm',
        'menu-items/:item' : 'itemDetails',
        'categories/:category' : 'categoryDetails'
    },

    list: function () {
        $('#app').html(this.menuView.render().el);
    },

    itemForm: function () {
        $('#app').html('New item form');
    },

    initialize: function () {
        this.menuCategoryView = new MenuCategoryView(
            {
                 category: 'Entree',
                 images: [
                     'carrots.jpg',
                     'green-beans.jpg',
                     'mashed-potatoes.jpg'
                 ]
            }
        );

        this.menuList = new MenuList();
        this.menuView = new MenuView({collection: this.menuItems});

        this.menuItemModel = new MenuItem();
        this.menuItemView = new MenuItemView(
            {
                 model: this.menuItemModel
            }
        );

        this.menuItems = new MenuItems();
        this.menuItems.fetch();
    },

    itemDetails: function (items) {
        this.menuItemModel.set('id', item);
        this.menuItemModel.fetch();
        $('app').html(this.menuItemView.render().el);
    },

    categoryDetails: funciton (category) {
        this.menuCategoryView.options.category = category;
        $('#app').html(this.menuCategoryView.render().el);
    }  

});

// Sorting Backbone.Collection

var MenuList = Backbone.Model.extend({
    defauts: {
        items: [
            'Garden Salad',
            'Pizza',
            'Cheesecake'
        ]
    }
});

var MenuItem = Backbone.Model.extend({
    defaults: {
        category: 'Entree',
        imagepath: 'no-image',
        name: ''
    },
    urlRoot: '/items'
});

var MenuItems = Backbone.Collection.extend({
    comparator: 'name', // Ascending order
/*    comparator: function(a,b) { // Descending sort order
        if (a.get('name') < b.get('name')) {
            return 1;
        } else if (a.get('name') > b.get('name')) {
            return -1;
        }
    }, */
    model: MenuItem,
    url: '/items'
});

var MenuCategoryView = Backbone.View.extend({
    template: Handlebars.compile(
        '<h1>{{category}}</h1>' +
        '{{#each images}}' +
        '<img src="photots/{{this}}" class="img-polaroid" />' +
        '{{/each}}'
     ),

    render: function(){
        this.$el.html(this.template(this.options));
        return this;
    }
});

var MenuView = Backbone.View.extend({
    template: Handlebars.compile(
        '<ul>' +
        '{{#each models}}' +
        '<li>{{attributes.name}}</li>' +
        '{{/each}}' +
        '</ul>'
    ),

    initialize: function() {
        this.listenTo(this.collection, 'reset', this.render);
    },

    render: function(){
        this.$el.html(this.template(this.collection));
        return this;
    }
});


var MenuItemDetails = Backbone.View.extend({
    template: Handlebars.compile(
        '<div>' +
        '<h1>{{name}}</h1>' +
        '<p><span class="label">{{category}}</span></p>' +
        '<img src="photos/{{imagepath}}" class="img-polaroid" />' +
        '</div>'
    ),

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

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

var AppRouter = Backbone.Router.extend({
    routes: {
        '': 'list',
        'menu-items/new' : 'itemForm',
        'menu-items/:item' : 'itemDetails',
        'categories/:category' : 'categoryDetails'
    },

    list: function () {
        $('#app').html(this.menuView.render().el);
    },

    itemForm: function () {
        $('#app').html('New item form');
    },

    initialize: function () {
        this.menuCategoryView = new MenuCategoryView(
            {
                 category: 'Entree',
                 images: [
                     'carrots.jpg',
                     'green-beans.jpg',
                     'mashed-potatoes.jpg'
                 ]
            }
        );

        this.menuList = new MenuList();
        this.menuView = new MenuView({collection: this.menuItems});

        this.menuItemModel = new MenuItem();
        this.menuItemView = new MenuItemView(
            {
                 model: this.menuItemModel
            }
        );

        this.menuItems = new MenuItems();
        this.menuItems.fetch();
    },

    itemDetails: function (items) {
        this.menuItemModel.set('id', item);
        this.menuItemModel.fetch();
        $('app').html(this.menuItemView.render().el);
    },

    categoryDetails: funciton (category) {
        this.menuCategoryView.options.category = category;
        $('#app').html(this.menuCategoryView.render().el);
    }  

});

// Getting Models data from Collection

var MenuList = Backbone.Model.extend({
    defauts: {
        items: [
            'Garden Salad',
            'Pizza',
            'Cheesecake'
        ]
    }
});

var MenuItem = Backbone.Model.extend({
    defaults: {
        category: 'Entree',
        imagepath: 'no-image',
        name: ''
    },
    urlRoot: '/items'
});

var MenuItems = Backbone.Collection.extend({
    comparator: 'name', // Ascending order
/*    comparator: function(a,b) { // Descending sort order
        if (a.get('name') < b.get('name')) {
            return 1;
        } else if (a.get('name') > b.get('name')) {
            return -1;
        }
    }, */
    model: MenuItem,
    url: '/items'
});

var MenuCategoryView = Backbone.View.extend({
    template: Handlebars.compile(
        '<h1>{{category}}</h1>' +
        '{{#each images}}' +
        '<img src="photots/{{this}}" class="img-polaroid" />' +
        '{{/each}}'
     ),

    render: function(){
        this.$el.html(this.template(this.options));
        return this;
    }
});

var MenuView = Backbone.View.extend({
    template: Handlebars.compile(
        '<ul>' +
        '{{#each models}}' +
        '<li>{{attributes.name}}</li>' +
        '{{/each}}' +
        '</ul>'
    ),

    initialize: function() {
        this.listenTo(this.collection, 'reset', this.render);
    },

    render: function(){
        this.$el.html(this.template(this.collection));
        return this;
    }
});


var MenuItemDetails = Backbone.View.extend({
    template: Handlebars.compile(
        '<div>' +
        '<h1>{{name}}</h1>' +
        '<p><span class="label">{{category}}</span></p>' +
        '<img src="photos/{{imagepath}}" class="img-polaroid" />' +
        '</div>'
    ),

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

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

var AppRouter = Backbone.Router.extend({
    routes: {
        '': 'list',
        'menu-items/new' : 'itemForm',
        'menu-items/:item' : 'itemDetails',
        'categories/:category' : 'categoryDetails'
    },

    list: function () {
        $('#app').html(this.menuView.render().el);
    },

    itemForm: function () {
        $('#app').html('New item form');
    },

    initialize: function () {
        this.menuCategoryView = new MenuCategoryView(
            {
                 category: 'Entree',
                 images: [
                     'carrots.jpg',
                     'green-beans.jpg',
                     'mashed-potatoes.jpg'
                 ]
            }
        );

        this.menuList = new MenuList();
        this.menuView = new MenuView({collection: this.menuItems});

        this.menuItemModel = new MenuItem();
        this.menuItemView = new MenuItemView(
            {
                 model: this.menuItemModel
            }
        );

        this.menuItems = new MenuItems();
        this.menuItems.fetch();
    },

    itemDetails: function (items) {
        this.menuItemView.model = this.menuItems.get(item);
        $('app').html(this.menuItemView.render().el);
    },

    categoryDetails: funciton (category) {
        this.menuCategoryView.options.category = category;
        $('#app').html(this.menuCategoryView.render().el);
    }  

});

// Adding Model to Collection

var MenuList = Backbone.Model.extend({
    defauts: {
        items: [
            'Garden Salad',
            'Pizza',
            'Cheesecake'
        ]
    }
});

var MenuItem = Backbone.Model.extend({
    defaults: {
        category: 'Entree',
        imagepath: 'no-image',
        name: ''
    },
    urlRoot: '/items'
});

var MenuItems = Backbone.Collection.extend({
    comparator: 'name', // Ascending order
/*    comparator: function(a,b) { // Descending sort order
        if (a.get('name') < b.get('name')) {
            return 1;
        } else if (a.get('name') > b.get('name')) {
            return -1;
        }
    }, */
    model: MenuItem,
    url: '/items'
});

var MenuCategoryView = Backbone.View.extend({
    template: Handlebars.compile(
        '<h1>{{category}}</h1>' +
        '{{#each images}}' +
        '<img src="photots/{{this}}" class="img-polaroid" />' +
        '{{/each}}'
     ),

    render: function(){
        this.$el.html(this.template(this.options));
        return this;
    }
});

var MenuView = Backbone.View.extend({
    template: Handlebars.compile(
        '<ul>' +
        '{{#each models}}' +
        '<li>{{attributes.name}}</li>' +
        '{{/each}}' +
        '</ul>'
    ),

    initialize: function() {
        this.listenTo(this.collection, 'reset', this.render);
        this.listenTo(this.collection, 'add', this.render);
        this.listenTo(this.collection, 'remove', this.render);
    },

    render: function(){
        this.$el.html(this.template(this.collection));
        return this;
    }
});


var MenuItemDetails = Backbone.View.extend({
    template: Handlebars.compile(
        '<div>' +
        '<h1>{{name}}</h1>' +
        '<p><span class="label">{{category}}</span></p>' +
        '<img src="photos/{{imagepath}}" class="img-polaroid" />' +
        '</div>'
    ),

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

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

var AppRouter = Backbone.Router.extend({
    routes: {
        '': 'list',
        'menu-items/new' : 'itemForm',
        'menu-items/:item' : 'itemDetails',
        'categories/:category' : 'categoryDetails'
    },

    list: function () {
        $('#app').html(this.menuView.render().el);
    },

    itemForm: function () {
        $('#app').html('New item form');
    },

    initialize: function () {
        this.menuCategoryView = new MenuCategoryView(
            {
                 category: 'Entree',
                 images: [
                     'carrots.jpg',
                     'green-beans.jpg',
                     'mashed-potatoes.jpg'
                 ]
            }
        );

        this.menuList = new MenuList();
        this.menuView = new MenuView({collection: this.menuItems});

        this.menuItemModel = new MenuItem();
        this.menuItemView = new MenuItemView(
            {
                 model: this.menuItemModel
            }
        );

        this.menuItems = new MenuItems();
        this.menuItems.fetch();
    },

    itemDetails: function (items) {
        this.menuItemView.model = this.menuItems.get(item);
        $('app').html(this.menuItemView.render().el);
    },

    categoryDetails: funciton (category) {
        this.menuCategoryView.options.category = category;
        $('#app').html(this.menuCategoryView.render().el);
    }  

});

app.menuItems;
app.menuItems.add({id: 'lemonade', name: 'Lemonade'});
app.menuItems.remove('lemonade');

// Extend Menu view

var OrdersView = BackboneView.extend({
    template: Handlebars.compile(
        '<h1>Order items</h1>' +
        '{{#each models}}' +
        '<img src="photos/{{attributes.imagepath}}" class="img-polaroid" />' +
        '{{/each}}'
    ),

    render: function() {
        this.$el.html(this.template(this.collection));
        return this;
    }
});

var MenuList = Backbone.Model.extend({
    defauts: {
        items: [
            'Garden Salad',
            'Pizza',
            'Cheesecake'
        ]
    }
});

var MenuItem = Backbone.Model.extend({
    defaults: {
        category: 'Entree',
        imagepath: 'no-image',
        name: ''
    },
    urlRoot: '/items'
});

var MenuItems = Backbone.Collection.extend({
    comparator: 'name', // Ascending order
/*    comparator: function(a,b) { // Descending sort order
        if (a.get('name') < b.get('name')) {
            return 1;
        } else if (a.get('name') > b.get('name')) {
            return -1;
        }
    }, */
    model: MenuItem,
    url: '/items'
});

var MenuCategoryView = Backbone.View.extend({
    template: Handlebars.compile(
        '<h1>{{category}}</h1>' +
        '{{#each images}}' +
        '<img src="photots/{{this}}" class="img-polaroid" />' +
        '{{/each}}'
     ),

    render: function(){
        this.$el.html(this.template(this.options));
        return this;
    }
});

var MenuView = Backbone.View.extend({
    template: Handlebars.compile(
        '<ul>' +
        '{{#each models}}' +
        '<li>{{attributes.name}}</li>' +
        '{{/each}}' +
        '</ul>'
    ),

    initialize: function() {
        this.listenTo(this.collection, 'reset', this.render);
        this.listenTo(this.collection, 'add', this.render);
        this.listenTo(this.collection, 'remove', this.render);
    },

    render: function(){
        this.$el.html(this.template(this.collection));
        return this;
    }
});


var MenuItemDetails = Backbone.View.extend({
    template: Handlebars.compile(
        '<div>' +
        '<h1>{{name}}</h1>' +
        '<p><span class="label">{{category}}</span></p>' +
        '<img src="photos/{{imagepath}}" class="img-polaroid" />' +
        '</div>'
    ),

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

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

var AppRouter = Backbone.Router.extend({
    routes: {
        '': 'list',
        'menu-items/new' : 'itemForm',
        'menu-items/:item' : 'itemDetails',
        'categories/:category' : 'categoryDetails',
        'orders/:item': 'orderItem'
    },

    list: function () {
        $('#app').html(this.menuView.render().el);
    },

    itemForm: function () {
        $('#app').html('New item form');
    },

    initialize: function () {
        this.menuCategoryView = new MenuCategoryView(
            {
                 category: 'Entree',
                 images: [
                     'carrots.jpg',
                     'green-beans.jpg',
                     'mashed-potatoes.jpg'
                 ]
            }
        );

        this.menuList = new MenuList();
        this.menuView = new MenuView({collection: this.menuItems});

        this.menuItemModel = new MenuItem();
        this.menuItemView = new MenuItemView(
            {
                 model: this.menuItemModel
            }
        );

        this.menuItems = new MenuItems();
        this.menuItems.fetch();

        this.orderdItems = new MenuItems();
        this.orderdView = new OrdersView({collection: this.orderedItems});
    },

    itemDetails: function (items) {
        this.menuItemView.model = this.menuItems.get(item);
        $('app').html(this.menuItemView.render().el);
    },

    categoryDetails: function (category) {
        this.menuCategoryView.options.category = category;
        $('#app').html(this.menuCategoryView.render().el);
    },  

    orderItem: function (item) {
        var menuItem = this.menuItems.get(item);
        this.orderedItems.add(menuItem);
       $('#app').html(this.ordersView.render().el);
    }

});

// REST verbs
GET
POST
PUT - UPDATE
DELETE

// Form

var MenuItemForm = Backbone.View.extend({
    template: Handlebars.compile(
        '<form class="form-horizontal">' +
            '<fieldset>' +
                '<legend>New Menu Item</legend>' +
                    '<div class="control-group">' +
                        '<input type="text" name="name" placeholder="Name" />' +
                    '</div>' +
                    '<div class="control-group">' +
                        '<input type="text" name="category" placeholder="Category" />' +
                    '</div>' +
                    '<div class="control-group">' +
                        '<input type="text" name="url" placeholder="URL" />' +
                    '</div>' +
                    '<div class="control-group">' +
                        '<input type="text" name="imagepath" placeholder="Path to image" />' +
                    '</div>' +    
                    '<button type="button" class="btn btn-danger">Cancel</button>' +
                    '<button type="button" class="btn btn-primary">Save</save>' +
            '</fieldset>' +
        '</form>'        
    ),

    events: {
        'click .btn-primary': 'setModelData'
    },

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

    setModelData: function () {
         this.model.set({
             name: this.$el.find('input[name="name"]').val(),
             category: this.$el.find('input[name="url"]').val(),
             url: this.$el.find('input[name="url"]').val(),
             imagepath: this.$el.find('input[name="imagepath"]').val()
         });
    }
});

var OrdersView = BackboneView.extend({
    template: Handlebars.compile(
        '<h1>Order items</h1>' +
        '{{#each models}}' +
        '<img src="photos/{{attributes.imagepath}}" class="img-polaroid" />' +
        '{{/each}}'
    ),

    render: function() {
        this.$el.html(this.template(this.collection));
        return this;
    }
});

var MenuList = Backbone.Model.extend({
    defauts: {
        items: [
            'Garden Salad',
            'Pizza',
            'Cheesecake'
        ]
    }
});

var MenuItem = Backbone.Model.extend({
    defaults: {
        category: 'Entree',
        imagepath: 'no-image',
        name: ''
    },
    urlRoot: '/items'
});

var MenuItems = Backbone.Collection.extend({
    comparator: 'name', // Ascending order
/*    comparator: function(a,b) { // Descending sort order
        if (a.get('name') < b.get('name')) {
            return 1;
        } else if (a.get('name') > b.get('name')) {
            return -1;
        }
    }, */
    model: MenuItem,
    url: '/items'
});

var MenuCategoryView = Backbone.View.extend({
    template: Handlebars.compile(
        '<h1>{{category}}</h1>' +
        '{{#each images}}' +
        '<img src="photots/{{this}}" class="img-polaroid" />' +
        '{{/each}}'
     ),

    render: function(){
        this.$el.html(this.template(this.options));
        return this;
    }
});

var MenuView = Backbone.View.extend({
    template: Handlebars.compile(
        '<ul>' +
        '{{#each models}}' +
        '<li>{{attributes.name}}</li>' +
        '{{/each}}' +
        '</ul>'
    ),

    initialize: function() {
        this.listenTo(this.collection, 'reset', this.render);
        this.listenTo(this.collection, 'add', this.render);
        this.listenTo(this.collection, 'remove', this.render);
    },

    render: function(){
        this.$el.html(this.template(this.collection));
        return this;
    }
});


var MenuItemDetails = Backbone.View.extend({
    template: Handlebars.compile(
        '<div>' +
        '<h1>{{name}}</h1>' +
        '<p><span class="label">{{category}}</span></p>' +
        '<img src="photos/{{imagepath}}" class="img-polaroid" />' +
        '</div>'
    ),

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

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

var AppRouter = Backbone.Router.extend({
    routes: {
        '': 'list',
        'menu-items/new' : 'itemForm',
        'menu-items/:item' : 'itemDetails',
        'categories/:category' : 'categoryDetails',
        'orders/:item': 'orderItem'
    },

    list: function () {
        $('#app').html(this.menuView.render().el);
    },

    itemForm: function () {
        $('#app').html(this.menuItemForm.render().el);
    },

    initialize: function () {
        this.menuCategoryView = new MenuCategoryView(
            {
                 category: 'Entree',
                 images: [
                     'carrots.jpg',
                     'green-beans.jpg',
                     'mashed-potatoes.jpg'
                 ]
            }
        );

        this.menuList = new MenuList();
        this.menuView = new MenuView({collection: this.menuItems});

        this.menuItemModel = new MenuItem();
        this.menuItemView = new MenuItemView(
            {
                 model: this.menuItemModel
            }
        );

        this.menuItems = new MenuItems();
        this.menuItems.fetch();

        this.orderdItems = new MenuItems();
        this.orderdView = new OrdersView({collection: this.orderedItems});

        this.menuItemForm = new MenuItemForm({model: new MenuItem()});
    },

    itemDetails: function (items) {
        this.menuItemView.model = this.menuItems.get(item);
        $('app').html(this.menuItemView.render().el);
    },

    categoryDetails: function (category) {
        this.menuCategoryView.options.category = category;
        $('#app').html(this.menuCategoryView.render().el);
    },  

    orderItem: function (item) {
        var menuItem = this.menuItems.get(item);
        this.orderedItems.add(menuItem);
       $('#app').html(this.ordersView.render().el);
    }

});

// Save data to server

var MenuItemForm = Backbone.View.extend({
    template: Handlebars.compile(
        '<form class="form-horizontal">' +
            '<fieldset>' +
                '<legend>New Menu Item</legend>' +
                    '<div class="control-group">' +
                        '<input type="text" name="name" placeholder="Name" />' +
                    '</div>' +
                    '<div class="control-group">' +
                        '<input type="text" name="category" placeholder="Category" />' +
                    '</div>' +
                    '<div class="control-group">' +
                        '<input type="text" name="url" placeholder="URL" />' +
                    '</div>' +
                    '<div class="control-group">' +
                        '<input type="text" name="imagepath" placeholder="Path to image" />' +
                    '</div>' +    
                    '<button type="button" class="btn btn-danger">Cancel</button>' +
                    '<button type="button" class="btn btn-primary">Save</save>' +
            '</fieldset>' +
        '</form>'        
    ),

    render: function () {
        this.$el.html(this.template());
        this.delegateEvents({
            'click .btn-primary': 'save'
        });
        return.this;
    },

    save: function(){
        this.setModelData();
        this.model.save(
            this.model.Attributes,
            {
                 success: function (model) {
                     app.menuItems.add(model);
                     app.navigate('manu-items/' + model.get('url'), {trigger: true});
                 }
            }
        );
    },

    setModelData: function () {
         this.model.set({
             id: null,
             name: this.$el.find('input[name="name"]').val(),
             category: this.$el.find('input[name="url"]').val(),
             url: this.$el.find('input[name="url"]').val(),
             imagepath: this.$el.find('input[name="imagepath"]').val()
         });
    }
});

var OrdersView = BackboneView.extend({
    template: Handlebars.compile(
        '<h1>Order items</h1>' +
        '{{#each models}}' +
        '<img src="photos/{{attributes.imagepath}}" class="img-polaroid" />' +
        '{{/each}}'
    ),

    render: function() {
        this.$el.html(this.template(this.collection));
        return this;
    }
});

var MenuList = Backbone.Model.extend({
    defauts: {
        items: [
            'Garden Salad',
            'Pizza',
            'Cheesecake'
        ]
    }
});

var MenuItem = Backbone.Model.extend({
    defaults: {
        category: 'Entree',
        imagepath: 'no-image',
        name: ''
    },
    urlRoot: '/items'
});

var MenuItems = Backbone.Collection.extend({
    comparator: 'name', // Ascending order
/*    comparator: function(a,b) { // Descending sort order
        if (a.get('name') < b.get('name')) {
            return 1;
        } else if (a.get('name') > b.get('name')) {
            return -1;
        }
    }, */
    model: MenuItem,
    url: '/items'
});

var MenuCategoryView = Backbone.View.extend({
    template: Handlebars.compile(
        '<h1>{{category}}</h1>' +
        '{{#each images}}' +
        '<img src="photots/{{this}}" class="img-polaroid" />' +
        '{{/each}}'
     ),

    render: function(){
        this.$el.html(this.template(this.options));
        return this;
    }
});

var MenuView = Backbone.View.extend({
    template: Handlebars.compile(
        '<ul>' +
        '{{#each models}}' +
        '<li>{{attributes.name}}</li>' +
        '{{/each}}' +
        '</ul>'
    ),

    initialize: function() {
        this.listenTo(this.collection, 'reset', this.render);
        this.listenTo(this.collection, 'add', this.render);
        this.listenTo(this.collection, 'remove', this.render);
    },

    render: function(){
        this.$el.html(this.template(this.collection));
        return this;
    }
});


var MenuItemDetails = Backbone.View.extend({
    template: Handlebars.compile(
        '<div>' +
        '<h1>{{name}}</h1>' +
        '<p><span class="label">{{category}}</span></p>' +
        '<img src="photos/{{imagepath}}" class="img-polaroid" />' +
        '</div>'
    ),

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

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

var AppRouter = Backbone.Router.extend({
    routes: {
        '': 'list',
        'menu-items/new' : 'itemForm',
        'menu-items/:item' : 'itemDetails',
        'categories/:category' : 'categoryDetails',
        'orders/:item': 'orderItem'
    },

    list: function () {
        $('#app').html(this.menuView.render().el);
    },

    itemForm: function () {
        $('#app').html(this.menuItemForm.render().el);
    },

    initialize: function () {
        this.menuCategoryView = new MenuCategoryView(
            {
                 category: 'Entree',
                 images: [
                     'carrots.jpg',
                     'green-beans.jpg',
                     'mashed-potatoes.jpg'
                 ]
            }
        );

        this.menuList = new MenuList();
        this.menuView = new MenuView({collection: this.menuItems});

        this.menuItemModel = new MenuItem();
        this.menuItemView = new MenuItemView(
            {
                 model: this.menuItemModel
            }
        );

        this.menuItems = new MenuItems();
        this.menuItems.fetch();

        this.orderdItems = new MenuItems();
        this.orderdView = new OrdersView({collection: this.orderedItems});

        this.menuItemForm = new MenuItemForm({model: new MenuItem()});
    },

    itemDetails: function (items) {
        this.menuItemView.model = this.menuItems.get(item);
        $('app').html(this.menuItemView.render().el);
    },

    categoryDetails: function (category) {
        this.menuCategoryView.options.category = category;
        $('#app').html(this.menuCategoryView.render().el);
    },  

    orderItem: function (item) {
        var menuItem = this.menuItems.get(item);
        this.orderedItems.add(menuItem);
       $('#app').html(this.ordersView.render().el);
    }

});

// Delete data from server

var MenuItemForm = Backbone.View.extend({
    template: Handlebars.compile(
        '<form class="form-horizontal">' +
            '<fieldset>' +
                '<legend>New Menu Item</legend>' +
                    '<div class="control-group">' +
                        '<input type="text" name="name" placeholder="Name" />' +
                    '</div>' +
                    '<div class="control-group">' +
                        '<input type="text" name="category" placeholder="Category" />' +
                    '</div>' +
                    '<div class="control-group">' +
                        '<input type="text" name="url" placeholder="URL" />' +
                    '</div>' +
                    '<div class="control-group">' +
                        '<input type="text" name="imagepath" placeholder="Path to image" />' +
                    '</div>' +    
                    '<button type="button" class="btn btn-danger">Cancel</button>' +
                    '<button type="button" class="btn btn-primary">Save</save>' +
            '</fieldset>' +
        '</form>'        
    ),

    render: function () {
        this.$el.html(this.template());
        this.delegateEvents({
            'click .btn-primary': 'save'
        });
        return.this;
    },

    save: function(){
        this.setModelData();
        this.model.save(
            this.model.Attributes,
            {
                 success: function (model) {
                     app.menuItems.add(model);
                     app.navigate('manu-items/' + model.get('url'), {trigger: true});
                 }
            }
        );
    },

    setModelData: function () {
         this.model.set({
             id: null,
             name: this.$el.find('input[name="name"]').val(),
             category: this.$el.find('input[name="url"]').val(),
             url: this.$el.find('input[name="url"]').val(),
             imagepath: this.$el.find('input[name="imagepath"]').val()
         });
    }
});

var OrdersView = BackboneView.extend({
    template: Handlebars.compile(
        '<h1>Order items</h1>' +
        '{{#each models}}' +
        '<img src="photos/{{attributes.imagepath}}" class="img-polaroid" />' +
        '{{/each}}'
    ),

    render: function() {
        this.$el.html(this.template(this.collection));
        return this;
    }
});

var MenuList = Backbone.Model.extend({
    defauts: {
        items: [
            'Garden Salad',
            'Pizza',
            'Cheesecake'
        ]
    }
});

var MenuItem = Backbone.Model.extend({
    defaults: {
        category: 'Entree',
        imagepath: 'no-image',
        name: ''
    },
    urlRoot: '/items'
});

var MenuItems = Backbone.Collection.extend({
    comparator: 'name', // Ascending order
/*    comparator: function(a,b) { // Descending sort order
        if (a.get('name') < b.get('name')) {
            return 1;
        } else if (a.get('name') > b.get('name')) {
            return -1;
        }
    }, */
    model: MenuItem,
    url: '/items'
});

var MenuCategoryView = Backbone.View.extend({
    template: Handlebars.compile(
        '<h1>{{category}}</h1>' +
        '{{#each images}}' +
        '<img src="photots/{{this}}" class="img-polaroid" />' +
        '{{/each}}'
     ),

    render: function(){
        this.$el.html(this.template(this.options));
        return this;
    }
});

var MenuView = Backbone.View.extend({
    template: Handlebars.compile(
        '<ul>' +
        '{{#each models}}' +
        '<li>{{attributes.name}}</li>' +
        '{{/each}}' +
        '</ul>'
    ),

    initialize: function() {
        this.listenTo(this.collection, 'reset', this.render);
        this.listenTo(this.collection, 'add', this.render);
        this.listenTo(this.collection, 'remove', this.render);
    },

    render: function(){
        this.$el.html(this.template(this.collection));
        return this;
    }
});


var MenuItemDetails = Backbone.View.extend({
    template: Handlebars.compile(
        '<div>' +
        '<h1>{{name}}</h1>' +
        '<p><span class="label">{{category}}</span></p>' +
        '<img src="photos/{{imagepath}}" class="img-polaroid" />' +
        '</div>' +
        '<p></p>' +
        '<button type="button" class="btn btn-danger confirm-delete">Delete</button>'
    ),

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

    render: function() {
        this.$el.html(this.template(this.model.attributes));
        this.delegateEvents({
            'click .btn-danger': 'deleteItem'
        });
        return this;    
    },

    deleteItem: function () {
        this.model.destroy(
            {
                 success: function (model) {
                     app.menuItems.remove(model.get('id'));
                     app.navigate('', {trigger: true});
                 }

             }
        );
    }
});

var AppRouter = Backbone.Router.extend({
    routes: {
        '': 'list',
        'menu-items/new' : 'itemForm',
        'menu-items/:item' : 'itemDetails',
        'categories/:category' : 'categoryDetails',
        'orders/:item': 'orderItem'
    },

    list: function () {
        $('#app').html(this.menuView.render().el);
    },

    itemForm: function () {
        $('#app').html(this.menuItemForm.render().el);
    },

    initialize: function () {
        this.menuCategoryView = new MenuCategoryView(
            {
                 category: 'Entree',
                 images: [
                     'carrots.jpg',
                     'green-beans.jpg',
                     'mashed-potatoes.jpg'
                 ]
            }
        );

        this.menuList = new MenuList();
        this.menuView = new MenuView({collection: this.menuItems});

        this.menuItemModel = new MenuItem();
        this.menuItemView = new MenuItemView(
            {
                 model: this.menuItemModel
            }
        );

        this.menuItems = new MenuItems();
        this.menuItems.fetch();

        this.orderdItems = new MenuItems();
        this.orderdView = new OrdersView({collection: this.orderedItems});

        this.menuItemForm = new MenuItemForm({model: new MenuItem()});
    },

    itemDetails: function (items) {
        this.menuItemView.model = this.menuItems.get(item);
        $('app').html(this.menuItemView.render().el);
    },

    categoryDetails: function (category) {
        this.menuCategoryView.options.category = category;
        $('#app').html(this.menuCategoryView.render().el);
    },  

    orderItem: function (item) {
        var menuItem = this.menuItems.get(item);
        this.orderedItems.add(menuItem);
       $('#app').html(this.ordersView.render().el);
    }

});