Batman.js is no longer in production at Shopify and is not actively maintained.

This website is left for reference (and for old times' sake).

batman.js

See the guide for an overview of view bindings. All bindings take a keypath as an attribute value unless otherwise specified.

Binding Description Example
data-bind Binds innerHTML or input value. Great for all kinds of inputs. <input type="checkbox" data-bind="user.isAuthorized" />
data-bind-#{attr} Binds node's attr to the keypath data-bind-id='post.id'
data-source innerHTML or value will get value from the keypath, but not set it <p data-source="post.content"></p>
data-source-#{attr} Node's attr will get its value from the keypath <h1 data-source-id='post.id'></h1>
data-target Changes to the node will set the keypath, but not get from the keypath <input data-target='user.name' />
data-context-#{name} Makes keypath value accessible as name inside node data-context-lastcomment='mostRecentPost.comments.last'
data-context Makes attributes of keypath directly accessible inside node
data-showif Shows node if keypath returns truthy data-showif='user.isAuthorized'
data-hideif Hides node if keypath returns truthy data-hideif='post.alreadyPublished'
data-insertif The node will be present in the DOM if the keypath returns truthy data-insertif='post.isPublished'
data-removeif The node will be removed from the DOM if the keypath returns truthy data-removeif='comment.isNotAppropriate'
data-renderif Node's contents will only be rendered if the keypath returns truthy
data-deferif Node's contents will only be rendered if the keypath returns falsey
data-route Takes a Batman.NamedRouteQuery. Clicking this node will route to the keypath. data-route='routes.posts[post].edit'
data-route-params Appends the keypath the route generated by a data-route on the node `data-route-params='myParamString'
data-view Wraps the node in the view specified data-view='NavigatorView'
data-view-#{optionName} Passes keypath value to data-view as optionName (see View.option) data-view-user='currentUser'
data-partial Takes a string, renders the template with that path data-partial='posts/_form'
data-defineview Wraps HTML which should be stored by Batman.View data-defineview='posts/index'
data-yield Creates a named yield for the application data-yield='modal'
data-contentfor Wraps content for a non-default yield data-contentfor='modal'
data-debug Calls debugger when instantiating the binding data-debug="true"
data-event-#{eventName} Binds keypath value to eventName on this node data-event-click='notifyAllUsers'
data-addclass-#{className} Adds class className if keypath returns truthy data-addclass-selected='item.selected'
data-removeclass-#{className} Removes class className if keypath returns truthy data-removeclass-highlight='item.isPast'
data-foreach-#{itemName} Make a copy of this node for each member in keypath; children accessible as itemName data-foreach-item='myCollection'
data-formfor-#{name} Inputs for keypath value will observe errors; keypath value aliased as name data-formfor-comment='newComment'
data-style-#{styleAttrName} Bind the node's style attribute styleAttrName to the keypath value data-style-float='leftOrRight'
data-track-#{clickOrView} Track clicks or views on this node. data-track-view='logView'

Keypath Filters

Binding keypaths may be passed through view filters. See the Batman.View Filters documentation for more information.

  • Custom Bindings

    To add a custom binding,

    • Extend an existing batman.js binding class
    • Create a binding hook as a property of Batman.DOM.readers or Batman.DOM.attrReaders (for bindings like data-binding-#{attr}).

    For example, a data-permission binding:

    # Usage:
    # <div data-permission='administrator'>
    #   <!-- equal to `data-showif='isAdministrator'` -->
    # </div>
    
    # Binding class
    class Batman.DOM.PermissionBinding extends Batman.DOM.ShowHideBinding
      constructor: (definition) ->
        permissionName = definition.keyPath
        permissionKeyPath = "is#{Batman.helpers.titleize(permissionName)}"
        definition.keyPath = permissionKeyPath
        super(definition)
    
    # Binding hook:
    Batman.DOM.readers.permission = (definition) ->
      new Batman.DOM.PermissionBinding(definition)
  • data-bind

    data-bind creates a two way binding between a property on a Batman.Object and an HTML element. Bindings created via data-bind will update the HTML element with the value of the JS land property as soon as they are created and each time the property changes after, and if the HTML element can be observed for changes, it will update the JS land property with the value from the HTML.

    data-bind will change its behaviour depending on what kind of tag it is attached to:

    • <input type="checkbox">: the binding will edit the checked property of the checkbox and populate the keypath with a boolean.
    • <input type="text"> and similar, <textarea>: the binding will edit the value property of the input and populate the keypath with the string found at value.
    • <input type="file">: the binding will not edit the value property of the input, but it will update the keypath with a host File object or objects if the node has the multiple attribute.
    • <select>: the binding will edit the selected property of each <option> tag within the <select> matching the property at the keypath. If the <select> has the multiple attribute, the value at the keypath can be an array of selected <option> values. You can also use data-bind-selected bindings on the individual options to toggle option selectedness.
    • All other tags: the binding will edit the innerHTML property of the tag and will not populate the keypath.

    data-bind can also be used to bind an attribute of a node to a JavaScript property. Since attributes can't be observed for changes, this is a one way binding which will never update the JavaScript land property. Specify which attribute to bind using the "double dash" syntax like so: data-bind-attribute="some.keypath". For example, to bind the placeholder attribute of an input, use data-bind-placeholder.

    <input type="text" data-bind-placeholder="'Specify a subtitle for product ' | append product.name">

    Note: data-bind will not update a JavaScript property if filters are used in the keypath.

  • data-source

    data-source creates a one way binding which propagates only changes from JavaScript land to the DOM, and never vice versa. data-source has the same semantics with regards to how it operates on different tags as data-bind, but it will only ever update the DOM and never the JavaScript land property.

    For example, the HTML below will never update the title property on the product, even if the user changes it. Each time the title attribute changes from a set in JavaScript land, the value of the input will be updated to the new value of title, erasing any potential changes that have been made to the value of the input by the user.

    <input type="text" data-source="product.title">

    Note: data-source-attribute is equivalent to data-bind-attribute, since the former is defined as never making JS land changes, and the latter is unable to.

  • data-target

    data-target creates a one way binding which propagates only changes from the DOM to JavaScript land, and never vice versa. data-target has the same semantics with regards to how it operates on different tags as data-bind, but it will never update the DOM even if the JavaScript land value changes.

    Note: data-target-attribute is unavailable, because DOM changes to node attributes can't be monitored.

  • data-showif / data-hideif

    data-showif and data-hideif bind to keypaths and show or hide the node they appear on based on the truthiness of the result. data-showif will show a node if the given keypath evaluates to something truthy, and data-hideif will leave a node visible until its given keypath becomes truthy, at which point the node will be hidden. data-showif and data-hideif show and hide nodes by adding display: none !important; to the node's style attribute.

    For example, if the HTML below is rendered where the keypath product.published evaluated to true, the <button> will be visible.

    <button data-showif="product.published">Unpublish Product</button>

    This is the Batman equivalent of a templating language's if construct, where else branches are implemented using the opposite binding.

    <button data-showif="product.published">Unpublish Product</button>
    <button data-hideif="product.published">Publish Product</button>
  • data-addclass / data-removeclass

    data-addclass and data-removeclass bindings can be used to conditionally add or remove a class from a node based on a boolean keypath. Specify the class to add using the "double dash" syntax; for example,data-addclass-big="some.keypath" on a node will add the "big" class to that node's classes if some.keypath is truthy. data-removeclass will remove a class (usually one which is present in the HTML) if the keypath passed to it is truthy.

    The outer span in the HTML below will have an "error" class when the product.errors.length keypath evaluates to anything other than 0, since 0 is falsy and other numbers are truthy.

    <p data-addclass-error="product.errors.length">
      This product has <span data-bind="product.errors.length"></span> errors.
    </p>
  • data-foreach

    data-foreach is used to loop over an iterable object in Batman views. data-foreach duplicates the node it occurs on for each item in the collection found at the keypath given to it, and renders each duplicated node with that node's object from the collection by putting it in the context under a name passed to it using the "double dash" syntax.

    The <option> node below will be duplicated for each item in the Set at the products keypath.

    <select>
      <option data-foreach-product="products" data-bind="product.name"></option>
    </select>

    Batman will execute the data-foreach binding before the data-bind on the <option> node, which means that the data-bind will be processed for each duplicated node with each separate Product in the products Set in scope for each separate node. If there were say 3 Products in the products set, the HTML would look similar to this once rendered:

    <select>
      <option data-bind="product.name">Product A</option>
      <option data-bind="product.name">Product B</option>
      <option data-bind="product.name">Product C</option>
      <!-- end products -->
    </select>

    data-foreach can be used to iterate over Batman.Sets, and most often should be, because it observes any Sets and will update the DOM with new nodes if items are added to the set, or remove nodes from the DOM if their corresponding nodes are removed from the set. data-foreach, like every other binding, is keypath aware, such that if the Set instance at the keypath changes, or any previous segment of the keypath changes, data-foreach will remove all the nodes currently in the DOM, and add new nodes for each new item in the incoming Set.

    Sometimes you'll need to add some custom logic to the iteration nodes. For example, a custom viewDidAppear handler so you can know whenever a new iteration node appears in the DOM. You can do this by specifying a custom subclass of Batman.IterationView.

    <ul>
      <li data-foreach-product="products" data-view="ProductIterationView">
        <span data-bind="product.name"></span>
      </li>
    </ul>
    class MyApp.ProductIterationView extends Batman.IterationView
      viewDidAppear: ->
        $(@get('node')).draggable()

    Note: data-foreach expects to find an iterable object at the keypath given to it, and will emit a warning if it finds undefined.

    Note: data-foreach expects the passed enumerable to be unique. It creates a map of nodes to items, so every node needs to be able to reference exactly one object. If you simply have a set of values that you're iterating over, you should wrap your values in objects, e.g. [{value: true}, {value: true}].

  • data-formfor

    data-formfor creates a special addition to the context stack to represent an object under edit within a form. Usually this object is a model. Using the double dash syntax, the name for the model to reside under can be specified.

    Automatic Validation Display

    data-formfor also has some handy functionality for displaying the result of validating the object under edit in the form. This will only be enabled if the object has an errors Set, like Batman.Models do.

    If a tag matching the relative selector .errors is found, it will populate this element with a list of the errors found during validation on the object. The selector for the errors container can be changed by adding a data-errors-list attribute with the value of the selector to the form with the data-formfor binding on it, or editing Batman.DOM.FormBinding::defaultErrorsListSelector.

    If value bindings are made using data-bind to attributes on the model within the form, automatic data-addclass-error bindings will be added to the elements on which the data-bind occurs to add the "error" class when the model has errors on the attribute which data-bind binds to.

    In the HTML below, an automatic data-addclass-error will be added to the <input> which activates when the product model has validation errors on the name attribute.

    <form data-formfor-product="currentProduct">
      <input type="text" data-bind="product.name"></input>
    </form>

    The class which gets automatically added to inputs binding to invalid attributes can be customized by editing Batman.DOM.FormBinding::errorClass.

  • data-context

    data-context bindings add the object found at the key to the context stack, optionally under a key using the double dash syntax.

    For example, if a product object exists in the current context, the data-context binding below will expose its attributes at the root level of the context:

    <div data-context="product">
      <span data-bind="name"></span>
      <span data-bind="cost"></span>
    </div>

    Contexts added to the stack can also be scoped under a key using data-context-:

    <div data-context-currentProduct="product">
      <span data-bind="currentProduct"></span>
      <span data-bind="currentProduct"></span>
    </div>

    This is a useful mechanism for passing local variables to partial views.

  • data-event

    data-event bindings add DOM event listeners to the nodes they exist on which call the function found at the passed keypath. data-event bindings use the double dash syntax to specify the name of the event to listen for.

    In the HTML below, if the keypath myFunction resolves to a function, that function will be executed each time the <button> element is clicked.

    <button data-event-click="myFunction">Execute myFunction!</button>

    You can use data-event to execute controller actions without routing:

    <!-- with beforeAction & afterAction filters: -->
    <a data-event-click='controllers.items.executeAction | withArguments "new"'>Create a new item</a>
    <!-- or, skipping beforeAction & afterAction filters: -->
    <a data-event-click="controllers.items.new">Create a new item</a>

    Functions which data-event calls will be passed the node and the DOMEvent object: (node, event) ->.

    data-event supports the following types of events formally and should "do the right thing" when attached to elements which fire these events:

    • click
    • doubleclick
    • change
    • submit

    If the event name used doesn't match the above events, the event name used will just fall through and be passed to window.addEventListener.

  • data-route

    data-route bindings are used to dispatch a new controller action upon the clicking of the node they bind to. data-route expects to find either a string or a NamedRouteQuery at the keypath passed to it. With this route, it will add an event handler to the click action of the element which dispatches the route and prevents the default action of the DOMEvent. data-route will also populate the href attribute if it occurs on an <a> tag so that other functions like "Copy Link Address" and Alt+Click continue to work on the link.

    The first way to use data-route is by passing it a string, which can be built using filters or an accessor, but the preferred way is to use the NamedRouteQuery. These objects are generated for you by starting keypaths at the App.routes property. All Batman.Apps have a routes property which holds a nested list of all the routes, which you descend into by passing various key segments and objects. Since the App object is present in the default context stack, data-route keypaths can just start with routes.

    For example, assume the following routes definition in the current Batman.App:

    class Alfred extends Batman.App
      @resources 'todos'

    This means that routes like /todos and /todos/:id exist. To route to the collection action, use the plural name of the resource:

    <a data-route="routes.todos"></a>

    To route to an individual todo things get a bit more complicated. If we have a Todo model with ID# 42 in the context as todo, use the get filter shorthand on the NamedRouteQuery returned by routes.todos to generate a member route:

    <a data-route="routes.todos[todo]"></a>

    Underneath, this is calling Alfred.get('routes.todos').get(todo); the todo object is being passed as a key to the NamedRouteQuery, which knows how to generate a member route when given a record. The above HTML when rendered will look like this:

    <a data-route="routes.todos[todo]" href="/todos/42"></a>

    This syntax can be extended to nested routes. If we have nested routes, we can use chained gets to generated nested routes

    class Tracker extends Batman.App
      @resources 'villains', ->
        @resources 'crimes'

    Routes for collection and member crimes should look like /villains/:villain_id/crimes and /villains/:villain_id/crimes/:id respectively. Assuming the presence of a villain and a crime in the context, chained gets on NamedRouteQuerys achieve this:

    <!-- Collection of crimes for a particular villain -->
    <a data-route="routes.villains[villain].crimes"></a>
    <!-- One crime of a particular villain -->
    <a data-route="routes.villains[villain].crimes[crime]"></a>

    Note: data-route bindings route only to internal dispatch, and not external links. Use a regular <a> tag to link away from the application.

  • data-route-params

    Adds the provided keypath or literal value to the route provided to data-route. For example:

    <a data-route='routes.villians.new' data-route-params='"mastermind=true"'>Mastermind</a>

    will route to /villians/new?mastermind=true and

    <a data-route='routes.villians.new' data-route-params='otherRouteParams'>Other</a>

    will look up otherRouteParams and append it to /villains/new, adding a ? if necessary.

  • data-view

    data-view bindings attach custom Batman.View instances or instantiate custom View subclasses to / on a node. data-view expects either a Batman.View instance or subclass at the keypath passed to it. If an instance is passed, it will set the node property of the view to the node the data-view occurs on. If a class is passed, that class will be instantiated with the context the data-view binding executed in and with the node it occurred upon. See Batman.View for more information on custom Views and their uses.

    Note: data-view bindings will bind to the passed keypath until it exists, that is to say until the value of it is not undefined. After the View has been set up, the data-view binding will remove itself and stop observing the keypath.

  • data-partial

    data-partial pulls in a partial template and renders it in the current context of the node the data-partial occurs in. data-partial expects the name of the view to render in the value of the HTML attribute. Warning: This value is not a keypath. The HTML attribute's value is interpreted as a string, and the template which resides at that view path will be rendered.

    If we have this HTML at views/villains/_stub.html in our app:

    <span data-bind="villain.name"></span>

    and in views/villains/show.html we have this HTML:

    <h1>A villain!</h1>
    <div data-partial="villains/_stub"></div>

    the contents of the stub partial will be inserted and rendered in the <div> above.

  • data-defineview

    data-defineview specifies that the content of the node defines the template for a particular view.

    The binding value should be a regular view path, i.e. "#{resource_name}/#{controller_action}". For example:

    <div data-defineview="crimes/index">
      <ul class="crimes">
        <li class="crime" data-foreach-crime="crimes">
          <span data-bind="crime.name" data-addclass-heinous="crime.heinous" />
        </li>
      </ul>
    </div>

    will be used by the CrimesController's index action.

  • data-renderif / data-deferif

    data-renderif (and data-deferif) defers parsing of a node's child bindings until its keypath updates to true (or false, respectively). Note that this does not prevent the node from being inserted into the DOM. These bindings should generally be combined with a data-showif or data-insertif to prevent it from being visible until it is ready.

    Deferring rendering can help prevent portions of the page updating many times while data is being loaded. It can also allow you to prevent features that are not yet ready from being used.

  • data-yield

    data-yield specifies that this node should be a render target for any view renderings that specify they should be rendered into a yield with this name. For example, data-yield="myYieldNode" can be rendered into by using new Batman.View(contentFor: 'myYieldNode'). The special case of data-yield="main" will be the render target for any view rendered by a controller action. This can mean the implicit render that happens by default at the end of a controller action or by explicitly calling @render inside a controller.

    You can also specify a render target inside your HTML using data-contentfor.

    <div data-yield="main"></div>
  • data-contentfor

    data-contentfor specifies that the content of this node should be rendered into a yield with the corresponding name.

    <div data-contentfor="header"><h1 data-bind="title"></h1></div>
    <div data-yield="header"></div>
  • data-track

    data-track-#{viewOrClick} fires events on an internal instance of a user-defined MyApp.EventTracker class. An EventTracker must respond to track(viewOrClick, keypathValue, node), where:

    • viewOrClick is the binding attribute argument
    • keypathValue is the value of the keypath passed to the binding
    • node is the bound node.

    The node is considered viewed when the binding is initialized.

    Under the hood, batman.js will instantiate MyApp.EventTracker once, storing it at Batman.Tracking.tracker.

  • data-debug

    Calls debugger when instantiating the binding. DebuggerBinding is removed from production builds of batman.js.

Help us improve our documentation!

Contributions to this page are welcome on Github. If you find a problem but you cannot fix it, please open an issue.

Discussion regarding batman.js documentation is also welcome on our mailing list.