Menu Example
This advanced example demonstrates how you can use Giraffe's features to build a route-powered menu with cached content views that save their scroll position.
var App, MenuView, MenuItemView, ContentView, ContentItemView;
The App
The App view creates a collection representing the menu's items along with the
menu and content views.
App = Giraffe.App.extend({
  initialize: function() {
    this.menuItems = new Giraffe.Collection([
      {name: 'menu item 1'},
      {name: 'menu item 2'},
      {name: 'menu item 3'}
    ]);
  },
  afterRender: function() {
    this.attach(new MenuView({collection: this.menuItems}));
    this.attach(new ContentView({collection: this.menuItems}));
  }
});
The MenuView
The MenuView listens for the 'route:menu' app event and activates the
collection item whose name matches the route parameter.
MenuView = Giraffe.View.extend({
  appEvents: {
    'route:menu': 'onRouteMenu'
  },
  onRouteMenu: function(menuItemName) {
    var activeMenuItem = this.collection.findWhere({active: true});
    if (activeMenuItem) activeMenuItem.set('active', false);
    this.collection.findWhere({name: menuItemName}).set('active', true);
  },
  dataEvents: {
    'change:active collection': 'onChangeActiveItem'
  },
  onChangeActiveItem: function(model, active) {
    if (active) this.render();
  },
  afterRender: function() {
    var self = this;
    this.collection.each(function(model) {
      self.attach(new MenuItemView({model: model}));
    });
  }
});
The MenuItemView
The MenuItemView takes a model and displays its name and active status.
MenuItemView = Giraffe.View.extend({
  template: '#menu-item-template',
  serialize: function() {
    var name = this.model.get('name');
    return {
      name: name,
      href: this.app.router.getRoute('route:menu', name),
      className: this.model.get('active') ? 'active' : ''
    };
  }
});
<script id="menu-item-template" type="text/template">
  <a href="<%= href %>" class="<%= className %>"><%= name %></a>
</script>
The ContentView
The ContentView listens for changes to the active property on its
collection and displays the appropriate ContentItemView.
ContentView = Giraffe.View.extend({
  dataEvents: {
    'change:active collection': 'onChangeActive'
  },
  onChangeActive: function(model, active) {
    if (active) this.render();
  },
  afterRender: function() {
    var activeMenuItem = this.collection.findWhere({active: true});
    if (activeMenuItem) {
      var itemView = this.getItemView(activeMenuItem);
      itemView.attachTo(this, {method: 'html'}); // first detaches any view in this.$el
    }
  },
  getItemView: function(menuItem) {
    var view = _.find(this.children, function(child) {
      return child.model === menuItem;
    });
    if (!view) {
      view = new ContentItemView({
        model: menuItem,
        disposeOnDetach: false, // keeps the view cached when detached
        saveScrollPosition: true
      });
    }
    return view;
  }
});
disposeOnDetach: false is passed to the ContentItemView because
by default, views are disposed when detached from the DOM, and by using
{method: 'html'} to attach the view, all views in the target element are first
detached. In this example we want to keep views around so they can save their
scroll position, but there are other reasons to cache views beyond saving view
state, like speed.
The ContentItemView
The ContentItemView displays the name of the content. Because these are
created with saveScrollPosition set to true, they save their scroll
position when detached and apply it when attached.
ContentItemView = Giraffe.View.extend({
  className: 'content-item-view',
  template: '#content-item-template',
  serialize: function() {
    var lines = [];
    for (var i = 0; i < 50; i++)
      lines.push(this.model.get('name'));
    return {lines: lines};
  }
});
<script id="content-item-template" type="text/template">
  <% _.each(lines, function(line) { %>
    <p>content for <%= line %></p>
  <% }); %>
</script>
Loading the App
We'll now create the app with some routes, which automatically creates an
instance of Giraffe.Router at app.router. Next we'll attach the app, start
Backbone.history, and then route to the first menu item.
var app = new App({
  routes: {
    'menu/:name': 'route:menu'
  }
});
app.attachTo('body');
Backbone.history.start();
app.router.cause('route:menu', 'menu item 1');