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!

Integrate Batman.js and Leaflet with a Custom View

batman.js views are one of the best ways to integrate other JS libraries with batman.js data structures like Batman.Object and Batman.Set. For example, you can use a custom view to display Batman.Models with leaflet.js

I’ve always wanted to try batman.js + leaflet. I had to:

  • Use @option to define view APIs
  • Initialize the custom view, controlling for async loading of data & map
  • Observe Batman.Objects to keep leaflet up-to-date.
  • Listen to leaflet to keep batman.js up to date

I ended up making an abstract LeafletView, implemented by LeafletPointView and LeafletCollectionPointView.

Be sure to check out the live example and source code (custom views, index html, edit html)!

@option in Custom Views

@option allows you to pass values explicitly into your custom view. That way, you can eliminate the guesswork of climbing the view tree or looking up to the controller for some value.

It provides a view binding and an accessor for your custom view. In my case, I used:

class App.LeafletView extends Batman.View
  @option 'draggable'

To provide in my HTML:

<div data-view='App.LeafletView' data-view-draggable='true'></div>

And in my view code:

@get('draggable') # => returns the value passed to the binding

This also works for objects, as in @option 'item':

<div data-view='App.LeafletPointView' data-view-item='monument'></div>

Then I have easy access to my record:


Initializing Custom Views

Initializing custom batman.js views is tough because:

  • Views are constructed before they’re added to the DOM
  • Bindings are initialized without values (and their objects may not be loaded from the server yet)
  • Lifecycle events may fire more than once

So, you have to be prepared for undefined values and for viewDidAppear to be fired more than once.

Keeping Other Libraries up to Date

Integrating batman.js with other JavaScript libraries usually means setting up event handlers so that events pass from an outside proxy of a Batman.Object to the object itself.

For example, to update a leaflet marker when a Batman.Object is changed, you have to observe the Batman.Object so that whenever latitude or longitude changes, you update the marker:

# From App.LeafletPointView, @get('item') returns the object
@observe 'item.latitude', (nv, ov) ->
  @updateMarker(@get('item'), centerOnItem: true) if nv?
@observe 'item.longitude', (nv, ov) ->
  @updateMarker(@get('item'), centerOnItem: true) if nv?

You have to link the other way too. To update a record when its marker is updated (by dragging), create a handler:

# from App.LeafletView
marker.on 'dragend', =>
  # ...
  # get values from leaflet and update batman.js
  latLng = marker.getLatLng()
  item.set 'latitude',
  item.set 'longitude', latLng.lng
  # ...

App.LeafletCollectionPointView uses Batman.SetObserver to track adding, removing and modifying items (just like Batman.SetSort).