Robert Mosolgo

To my knowledge, batman.js is not maintained. For that reason, I don't suggest that you use it for a new project!

Dynamic Navigation View with Batman.js

Creating a dynamic, context-based navigation menu is a breeze with batman.js and batman-rails thanks to Batman’s data-route view binding and the object[key] view filter. Here’s how I did it.

I have Rails/Batman app generated with batman-rails generators. I’m using Slim-assets for my Batman HTML. Source of the app is here.


My app allows users to design crepes, and so far it has two models, Crepes and Ingredients:

```coffeescript /app/assets/javascripts/batman/models/ class Creperie.Crepe extends Creperie.ApplicationModel # … stuff toString: -> “#{@get(‘name’)} ($#{@get(‘price’)})”

```coffeescript /app/assets/javascripts/batman/models/
class Creperie.Ingredient extends Creperie.ApplicationModel
  # ... stuff
  toString: ->
    "#{@get('name')} (#{@get('category')})"

I defined toString on both of the models because when a JS object instance is rendered as text, toString is called automatically.


My app defines /crepes and /ingredients, and I want each of those pages to show a side-bar nav that lists all items and provides a link to create a new item. So, I added a nav to my Rails layout where I will render my Creperie.ContextNavView. I attach the Batman.View with the data-view view binding:

```ruby /app/views/layouts/batman.html.slim doctype html html head title data-bind=’Title | default “Home” | prepend “Creperie | “’ = stylesheet_link_tag “application”, :media => “all” = javascript_include_tag “creperie” = csrf_meta_tags body header section h1 Creperie! nav data-view=’NavBarView’

/ here is my context view:
nav data-view='ContextNavView'

section#main data-yield='main'

script type=”text/javascript” |;

# View

Then, I defined a custom [Batman.View]( called `ContextNavView`.

I defined the prototype's source attribute to point to the HTML template which I will create next. That way, Batman knows where to find the HTML for this view. This is the same as passing the source to the constructor, eg, `new Creperie.ContextNavView(source: 'layouts/context_nav')`.

I also defined the `viewDidAppear` hook for this view. You can define hooks for any point in a [`Batman.View`'s lifecycle]( I set up an [observer]( on my app's `currentRoute.controller`.

```coffeescript /app/assets/javascripts/batman/views/
class Creperie.ContextNavView extends Creperie.ApplicationView
  source: 'layouts/context_nav' # my HTML template

  viewDidAppear: ->
    Creperie.observe "currentRoute.controller", (newValue, oldValue) ->
      currentController = newValue
      itemClassName = Batman.helpers.singularize(
        ) # camelize and singularize the controller name
      itemClass = Creperie[itemClassName]
      if itemClass?
        @set 'itemClass', itemClass
        @set 'itemRoute', currentController

This is made possible because Batman (v 0.15.0) keeps track of the current route at MyApp.currentRoute (which you can access in your code or in the console as MyApp.get('currentRoute')). Since my controllers are all defined with Rails-style names, I can count on the controller names matching the model that I want to display.


Last, I defined the template which ContextNavView uses as its source:

ruby /app/assets/javascripts/batman/html/layouts/context_nav.html.slim ul.items li.item data-foreach-item='itemClass.all' a data-route='routes[itemRoute][item]' span data-bind='item' a.edit data-route='routes[itemRoute][item].edit' edit a data-route='routes[itemRoute].new' span New  span data-bind=''

The big win here was sending strings to App.routes with [] in the keypath. That way, I could meta-program my routes – I didn’t have to make them explicit.

So what?

  • Use MyApp.currentRoute to get information about the current page.
  • Subclass Batman.View to provide site-wide navs (or other views).
  • Meta-program your routes (or other parts of the nav) by using [] in your keypaths.