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
@optionto 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:
@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
observeOnceto fire on change fromundefinedto some value. My case was different because I had to wait for the binding and for the map to load, hence theleafletReadyevent. - Check for initialization in
viewDidAppearhandlers
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).