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.Model
s 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.Object
s 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:
@get('item')
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.
- Use
observeOnce
to fire on change fromundefined
to some value. My case was different because I had to wait for the binding and for the map to load, hence theleafletReady
event. - Check for initialization in
viewDidAppear
handlers
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', latLng.lat
item.set 'longitude', latLng.lng
# ...
App.LeafletCollectionPointView
uses Batman.SetObserver
to track adding, removing and modifying items (just like Batman.SetSort
).