Quick explanatory comment

Quick explanatory comment

Anno.js
=======
Interactive step-by-step guides for web apps.

Anno.js is built to be absurdly extensible, but still works great out of the box (and looks damn fine doing it).

The MIT License (MIT)
---------------------

Copyright (c) 2013 Dan Fox

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

    class Anno

Creating an Anno object
-----------------------

An Anno object represents a single annotation on your page.  Each Anno has a `target` jQuery selector
and a `content` string as well as many other properties. For example:
```
  pizzaAnno = new Anno({
    target: '#pizza-list',
    content: 'Choose your pizza from the list below.'
  })
  pizzaAnno.show()
```
This constructor lets you override these properties and uses defaults for anything you don't specify.

      constructor: (options) ->
        if options instanceof Anno
          console.warn 'Anno constructor parameter is already an Anno object.'
        if not options?
          console.warn "new Anno() created with no options.  It's recommended to supply at least target and content."
        for key,val of options when key in ['chainTo', 'start', 'show', 'hide', 'hideAnno', 'chainSize', 'chainIndex', 'version']
          console.warn "Anno: Overriding '#{key}' is not recommended.  Can you override a delegated function instead?"

        for key,val of options
          this[key]=val

In practise, I usually like to specify `buttons` and `position`. You may also want to override `onShow`
and `onHide` callbacks, `className` and even `overlayElem()` for complete control.

If you find yourself setting the same property on every Anno object you create, you can
set default values at the top of your script that will apply to every Anno object from then onwards.

      @setDefaults: (options) ->
        for key,val of options
          Anno::[key] = val

Making a step-by-step tour
--------------------------

Anno objects can be chained together to make a sequential tour.

      chainTo: (obj) ->
        if obj?
          if not @_chainNext? # this is the end of the chain, add obj to the end.
            @_chainNext = if obj instanceof Anno then obj else new Anno(obj)
            @_chainNext._chainPrev = this
          else # pass the obj further along
            @_chainNext.chainTo(obj)
        else
          console.error "Can't chainTo a null object."
        return this

      _chainNext: null
      _chainPrev: null

Long Anno chains can also be made using the `Anno.chain()` shorthand.
```
 var annoTour = Anno.chain([
   {
     target: '.pizza-list',
     content: 'Choose your pizza from the list below.'
   }, {
     target: '#address-form',
     content: "Enter your address and we'll deliver your pizza",
     position: 'left'
   }
 ])
```
Note, the `annoTour` variable still only points to the single Anno object (for `.pizza-list`),
we've just chained another one onto it anonymously.

      @chain: (array) ->
        head = new Anno( array.shift() )
        if array.length >= 1 # array is now the tail.
          head.chainTo( Anno.chain(array) )
        return head

      chainSize: () ->
        if @_chainNext? then @_chainNext.chainSize() else 1+@chainIndex()

      # `anno.chainIndex(x)` gets the xth object in the chain, `anno.chainIndex()` gets the current index;
      chainIndex: (index) ->
        if index?
          (find = (curr, i, u) ->
            if curr?
              ci = curr.chainIndex()
              if      0 <= ci < i  then find(curr._chainNext, i, u)
              else if i <  ci <= u then find(curr._chainPrev, i, u)
              else if   ci is i    then curr
            else console.error "Couldn't switch to index '#{i}'. Chain size is '#{u}'"
          )(this, index, @chainSize())
        else
          if @_chainPrev? then 1+@_chainPrev.chainIndex() else 0

Hiding and showing annotations
------------------------------

`anno.show()` displays your annotation on top of a nice overlay and executes a callback.
All methods used here can be overridden in the same way we changed the `content` property.

Animations are all done with 300ms CSS transitions, so you can change your UI without touching any javascript.

      show: () -> # TODO warn if this Anno has already been shown.
        $target = @targetFn()
        if @_annoElem? then console.warn "Anno elem for '#{@target}' has already been generated.  Did you call show() twice?"
        @_annoElem = @annoElem()

        @showOverlay()
        @emphasiseTarget()

        $target.after(@_annoElem) # insert into DOM

        @_annoElem.addClass('anno-target-'+@arrowPositionFn())
        @positionAnnoElem()

        setTimeout (() => @_annoElem.removeClass('anno-hidden')), 10 # hack to make Chrome render the opacity:0 state.

        $target.scrollintoview()
        setTimeout (() => @_annoElem.scrollintoview()) , 300 #TODO fix jumpiness

        lastButton = @_annoElem.find('button').last()
        if @rightArrowClicksLastButton
          lastButton.keydown( (evt) -> if evt.keyCode is 39 then $(this).click()  ) # right arrow
        if @autoFocusLastButton
          lastButton.focus() if $target.find(':focus').length is 0 # don't steal focus from inside target element

        @_returnFromOnShow = @onShow(this, $target, @_annoElem)
        return this

      start: () -> @show() # alias because `tour.start()` sounds nicer than `tour.show()`

      rightArrowClicksLastButton: true
      autoFocusLastButton: true

The `onShow` callback does nothing by default, but can be very useful when overridden (e.g. to register a click
listener on the target element.)  Whatever value you return from the `onShow` function will get passed to the
`onHide` callback.  This can be used to unbind event listeners.

      onShow: (anno, $target, $annoElem) ->

      _returnFromOnShow = null

Hiding is done in two stages so that you can re-use one overlay element for a long chain of Anno's.

      hide: () ->
        @hideAnno()
        @hideOverlay()
        return this

      hideAnno: () ->
        @deemphasiseTarget()

        if @_annoElem?
          @_annoElem.addClass('anno-hidden')
          setTimeout () =>
            @_annoElem?.remove() # this method causes hideAnno to get called twice sometimes -> bad.
            @_annoElem = null
          , 300

          @onHide(this, @targetFn(), @_annoElem, @_returnFromOnShow)
        else
          console.warn "Can't hideAnno() for '#{@target}' when @_annoElem is null.  Did you call hideAnno() twice?"

        return this

      onHide: (anno, $target, $annoElem, returnFromOnShow) ->

`switchTo` displays another Anno and reuses the old overlay.

      switchTo: (otherAnno) ->
        if otherAnno?
          @hideAnno() # TODO: prevent this call to `hideAnno()` if the current Anno isn't shown
          otherAnno.show()
        else
          console.warn "Can't switchTo a null object. Hiding completely instead. "
          @hide() # this recovers from a programmer mistake by removing the overlay too

      switchToChainNext: () -> @switchTo @_chainNext

      switchToChainPrev: () -> @switchTo @_chainPrev

Customizing target
------------------

Specify a `target` jQuery selector to link your annotation to the DOM.

      target: 'h1'

`targetFn()` is used internally to return the first element matching your `target` selector (wrapped as a jQuery object).

      targetFn: () ->
        if typeof @target is 'string'
          r = $(@target).filter(':not(.anno-placeholder)') # .anno-placeholder is a clone to prevent text wrapping
          if r.length is 0 then console.error "Couldn't find Anno.target '#{@target}'."
          if r.length > 1 then console.warn "Anno target '#{@target}' matched #{r.length} elements. Targeting the first one."
          r.first()
        else if @target instanceof jQuery
          if @target.length > 1 then console.warn "Anno jQuery target matched #{@target.length} elements. Targeting the first one."
          return @target.first()
        else if @target instanceof HTMLElement
          $(@target)
        else if typeof @target is 'function'
          @target()
        else
          console.error "Unrecognised Anno.target. Please supply a jQuery selector string, a jQuery "+
              "object, a raw DOM element or a function returning a jQuery element. target:"
          console.error @target

`annoElem()` generates the jQuery object that will be inserted into the DOM.

      annoElem: () -> # TODO: should `annoElem()` encapsulate the re-use of one object?
        @_annoElem = $("""

""")
        @_annoElem.find('.anno-inner').
          append( @contentElem() ).
          append( @buttonsElem() ) # these a jquery elements, not HTML strings.
        return @_annoElem # NB: returning the original pointer each time breaks button click events...

      _annoElem: null

CSS classes can be included, e.g. .anno-width-150, 175, 200, 250 (default 300)

      className: ''

Content
-------

      content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'

      # Override this instead of `content` if you don't know the content in advance
      contentFn: () -> @content

      contentElem: () -> $("
"+@contentFn()+"
") # TODO: evaluate how easy it would be to change Anno content while its displayed.

Semi-transparent overlay and other effects
------------------------------------------

      showOverlay: () ->
        if $('.anno-overlay').length is 0
          $('body').append(e = @overlayElem().addClass 'anno-hidden') # TODO: write about pointer-events: none
          setTimeout (() -> e.removeClass 'anno-hidden'), 10
        else
          $('.anno-overlay').replaceWith @overlayElem()

      overlayElem: () ->
        $("

").
          click( (evt) => @overlayClick.call(this, this, evt) )

      overlayClassName: '' # TODO talk about .anno-hidden
      overlayClick: (anno, evt) -> anno.hide()

      hideOverlay: () ->
        $('.anno-overlay').addClass 'anno-hidden'
        setTimeout (() -> $('.anno-overlay').remove()), 300

      emphasiseTarget: ($target = @targetFn()) ->
        $target.closest(':scrollable').on 'mousewheel', (evt) ->  # TODO: register & remove a specific listener ... would this ruin existing jQuery scroll functions?
          evt.preventDefault()
          evt.stopPropagation()
        @_undoEmphasise.push ($t) -> $t.closest(':scrollable').off('mousewheel')

        if $target.css('position')  is 'static'
          $target.after(@_placeholder = $target.clone().addClass('anno-placeholder')) # ensures that the jquery :first selector in targetFn works.
          @_undoEmphasise.push ($t) => @_placeholder?.remove()
          ((a) => @_undoEmphasise.push ($t) -> $t.css position:a )($target.prop('style').position)
          $target.css( position:'absolute' )

          # if switching to position absolute has caused a dimension collapse, manually set H/W.
          if $target.outerWidth() isnt @_placeholder.outerWidth()
            # Find the current inline style on $target and make an undo function using that value
            ((a) => @_undoEmphasise.push ($t) -> $t.css width:a )($target.prop('style').width)
            $target.css('width', @_placeholder.outerWidth())
          if $target.outerHeight() isnt @_placeholder.outerHeight()
            ((a) => @_undoEmphasise.push ($t) -> $t.css height:a )($target.prop('style').height)
            $target.css('height', @_placeholder.outerHeight())

          # if switching to position absolute has caused a position change, manually set it too
          ppos = @_placeholder.position()
          tpos = $target.position()
          if tpos.top  isnt ppos.top
            ((a) => @_undoEmphasise.push ($t) -> $t.css top:a )($target.prop('style').top)
            $target.css('top', ppos.top)
          if tpos.left isnt ppos.left
            ((a) => @_undoEmphasise.push ($t) -> $t.css left:a )($target.prop('style').left)
            $target.css('left', ppos.left)

        if $target.css('background') is 'rgba(0, 0, 0, 0) none repeat scroll 0% 0% / auto padding-box border-box'
          ((a) => @_undoEmphasise.push ($t) -> $t.css background:a )($target.prop('style').background)
          $target.css( background: 'white')

        ((a) => @_undoEmphasise.push ($t) -> $t.css zIndex:a )($target.prop('style').zIndex)
        $target.css( zIndex:'1001' )

        return $target

      _undoEmphasise: [] # a list of functions to undo the effects of emphasiseTarget()
      _placeholder: null

      deemphasiseTarget: () ->
        $target = @targetFn()
        fn($target) for fn in @_undoEmphasise
        return $target

Positioning
-----------

The `position` property decides where your annotation will be displayed.

Alternatively, you can supply a hash of CSS attributes:
```
  position = {
    top: '10px',
    left: '57px'
  }
```
If you omit the `position` attribute entirely, Anno will use its best guess.

      position: null # any of `top`, `left`, `bottom`, `right`, `center-top`, `center-left`, `center-bottom` or `center-right`.

`positionAnnoElem()` sets the CSS of the Anno element so that it appears next to your target in a sensible way.

      positionAnnoElem: (annoEl = @_annoElem) -> # Must be called after DOM insertion.
        pos = @positionFn()

        $targetEl = @targetFn()

        offset = $targetEl.position()
        switch pos
          when 'top', 'bottom'
            annoEl.css(left: offset.left+'px')
          when 'center-top', 'center-bottom'
            annoEl.css(left: offset.left+($targetEl.outerWidth()/2 - annoEl.outerWidth()/2)+'px')
          when 'left', 'right'
            annoEl.css(top: offset.top+'px')
          when 'center-left', 'center-right'
            annoEl.css(top: offset.top+($targetEl.outerHeight()/2 - annoEl.outerHeight()/2)+'px')

        switch pos
          when 'top', 'center-top'
            annoEl.css( top: offset.top-annoEl.outerHeight()+'px')
          when 'bottom', 'center-bottom'
            annoEl.css( top: offset.top+$targetEl.outerHeight()+'px')
          when 'left', 'center-left'
            annoEl.css(left: offset.left-annoEl.outerWidth()+'px')
          when 'right', 'center-right'
            annoEl.css(left: offset.left+$targetEl.outerWidth()+'px')
          else
            if pos.left? or pos.right? or pos.top? or pos.bottom?
             annoEl.css(pos)
            else
              console.error "Unrecognised position: '#{pos}'"

        return annoEl

`positionFn()` returns the `position` property or tries to guess one if you left it blank.

      positionFn: () -> # Must be called after DOM insertion.
        if @position?
          return @position
        else if @_annoElem?
          $target = @targetFn()

          $container = $target.closest(':scrollable')
          $container = $('body') if $container.length is 0

          targetOffset = $target.offset()         # both relative to document
          containerOffset = $container.offset()
          targetBounds =
            left: targetOffset.left - containerOffset.left
            top:  targetOffset.top - containerOffset.top
          targetBounds.right = targetBounds.left + $target.outerWidth() # dist from left edge of $container to right of elem
          targetBounds.bottom = targetBounds.top + $target.outerHeight()

          viewBounds =
            w: $container.width() or $container.width() # TODO: what is this???
            h: $container.height() or $container.height()

          annoBounds =
            w: @_annoElem.outerWidth()
            h: @_annoElem.outerHeight()

          bad = []

          if annoBounds.w > targetBounds.left then bad = bad.concat ['left', 'center-left']
          if annoBounds.h > targetBounds.top  then bad = bad.concat ['top', 'center-top']
          if annoBounds.w + targetBounds.right  > viewBounds.w  then bad = bad.concat ['right', 'center-right']
          if annoBounds.h + targetBounds.bottom > viewBounds.h then bad = bad.concat ['bottom', 'center-bottom']

          allowed = Anno.preferredPositions.filter (p) -> p not in bad
          if allowed.length is 0
            console.error "Anno couldn't guess a position for '#{@target}'. Please supply one in the constructor."
          else
            console.warn "Anno: guessing position:'#{allowed[0]}' for '#{@target}'. "+
              "Possible Anno.preferredPositions: [#{allowed}]."
          return @position = allowed[0] # store this value for later - saves recomputing.

When there are several different positions that the Anno element could by displayed, `positionFn()` chooses
the first one available in `Anno.preferredPositions`.

      @preferredPositions = ['bottom', 'right', 'left', 'top',  # TODO: would it ever guess center-*?
              'center-bottom', 'center-right', 'center-left', 'center-top'] # TODO order these based on research.

`arrowPositionFn()` returns which way the arrow should point. (Normally just the opposite of the anno position.)

      arrowPositionFn: () ->
        if @arrowPosition?
          return @arrowPosition
        else if typeof @positionFn() is 'string'
          return {
            'top': 'bottom'
            'center-top': 'center-bottom'
            'left': 'right'
            'center-left' : 'center-right'
            'right' : 'left'
            'center-right' : 'center-left'
            'bottom': 'top'
            'center-bottom' : 'center-top'
          }[@positionFn()]
        else
          pos = l : parseInt(@positionFn().left, 10) ,  t : parseInt(@positionFn().top, 10)
          if Math.abs(pos.l) > Math.abs(pos.t)
            r = if pos.l < 0 then 'center-right' else 'center-left'
          else
            r = if pos.t < 0 then 'center-bottom' else 'center-top'
          console.warn "Guessing arrowPosition:'#{r}' for #{@target}. Include this in your constructor for consistency."
          return r

Override this if you've supplied a CSS hash as the `position` property. Can be any of `top`, `left`, etc.

      arrowPosition: null # TODO replace 'arrowPosition' with 'arrowDirection'

Customizing Buttons
-------------------

By default, Annotations have a single button, filled with default values from the `AnnoButton` class.

      buttons:  [ {} ]

      # returns a list of `AnnoButton` objects
      buttonsFn: () ->
        if @buttons instanceof Array
          @buttons.map (b) -> new AnnoButton(b)
        else
          [new AnnoButton(@buttons)] # in the else branch `@buttons` is a single hash

`buttonsElem()` produces the HTML for all those buttons (as a jQuery object).

      buttonsElem: () ->
        return $("

").
          append (b.buttonElem(this) for b in @buttonsFn())

AnnoButton
==========

    class AnnoButton

      constructor: (options) ->
        for key,val of options
          this[key]=val

      buttonElem: (anno) ->
        return $("<button class='anno-btn'></button>").
          html( @textFn(anno) ).
          addClass( @className ).
          click( (evt) => @click.call(anno, anno, evt) )

      textFn: (anno) ->
        if @text? then @text
        else if anno._chainNext? then 'Next' else 'Done'

      text: null

      className: ''

`click` is called when your button is clicked.  Note, the `this` keyword is bound to the parent
Anno object.

      click: (anno, evt) ->
        if anno._chainNext?
          anno.switchToChainNext()
        else
          anno.hide()

These are some handy presets that you can use by adding `AnnoButton.NextButton` to your Anno object's
`buttons` list.

      @NextButton: new AnnoButton({ text: 'Next' , click: () -> @switchToChainNext()  })

      @DoneButton: new AnnoButton({ text: 'Done' , click: () -> @hide()  })

      @BackButton: new AnnoButton(
          text: 'Back'
          className: 'anno-btn-low-importance'
          click: () -> @switchToChainPrev()
        )

Anno.js
=======
Interactive step-by-step guides for web apps.

Anno.js is built to be absurdly extensible, but still works great out of the box (and looks damn fine doing it).

The MIT License (MIT)
---------------------

Copyright (c) 2013 Dan Fox

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

    class Anno

Creating an Anno object
-----------------------

An Anno object represents a single annotation on your page.  Each Anno has a `target` jQuery selector
and a `content` string as well as many other properties. For example:
```
  pizzaAnno = new Anno({
    target: '#pizza-list',
    content: 'Choose your pizza from the list below.'
  })
  pizzaAnno.show()
```
This constructor lets you override these properties and uses defaults for anything you don't specify.

      constructor: (options) ->
        if options instanceof Anno
          console.warn 'Anno constructor parameter is already an Anno object.'
        if not options?
          console.warn "new Anno() created with no options.  It's recommended to supply at least target and content."
        for key,val of options when key in ['chainTo', 'start', 'show', 'hide', 'hideAnno', 'chainSize', 'chainIndex', 'version']
          console.warn "Anno: Overriding '#{key}' is not recommended.  Can you override a delegated function instead?"

        for key,val of options
          this[key]=val

In practise, I usually like to specify `buttons` and `position`. You may also want to override `onShow`
and `onHide` callbacks, `className` and even `overlayElem()` for complete control.

If you find yourself setting the same property on every Anno object you create, you can
set default values at the top of your script that will apply to every Anno object from then onwards.

      @setDefaults: (options) ->
        for key,val of options
          Anno::[key] = val

Making a step-by-step tour
--------------------------

Anno objects can be chained together to make a sequential tour.

      chainTo: (obj) ->
        if obj?
          if not @_chainNext? # this is the end of the chain, add obj to the end.
            @_chainNext = if obj instanceof Anno then obj else new Anno(obj)
            @_chainNext._chainPrev = this
          else # pass the obj further along
            @_chainNext.chainTo(obj)
        else
          console.error "Can't chainTo a null object."
        return this

      _chainNext: null
      _chainPrev: null

Long Anno chains can also be made using the `Anno.chain()` shorthand.
```
 var annoTour = Anno.chain([
   {
     target: '.pizza-list',
     content: 'Choose your pizza from the list below.'
   }, {
     target: '#address-form',
     content: "Enter your address and we'll deliver your pizza",
     position: 'left'
   }
 ])
```
Note, the `annoTour` variable still only points to the single Anno object (for `.pizza-list`),
we've just chained another one onto it anonymously.

      @chain: (array) ->
        head = new Anno( array.shift() )
        if array.length >= 1 # array is now the tail.
          head.chainTo( Anno.chain(array) )
        return head

      chainSize: () ->
        if @_chainNext? then @_chainNext.chainSize() else 1+@chainIndex()

      # `anno.chainIndex(x)` gets the xth object in the chain, `anno.chainIndex()` gets the current index;
      chainIndex: (index) ->
        if index?
          (find = (curr, i, u) ->
            if curr?
              ci = curr.chainIndex()
              if      0 <= ci < i  then find(curr._chainNext, i, u)
              else if i <  ci <= u then find(curr._chainPrev, i, u)
              else if   ci is i    then curr
            else console.error "Couldn't switch to index '#{i}'. Chain size is '#{u}'"
          )(this, index, @chainSize())
        else
          if @_chainPrev? then 1+@_chainPrev.chainIndex() else 0

Hiding and showing annotations
------------------------------

`anno.show()` displays your annotation on top of a nice overlay and executes a callback.
All methods used here can be overridden in the same way we changed the `content` property.

Animations are all done with 300ms CSS transitions, so you can change your UI without touching any javascript.

      show: () -> # TODO warn if this Anno has already been shown.
        $target = @targetFn()
        if @_annoElem? then console.warn "Anno elem for '#{@target}' has already been generated.  Did you call show() twice?"
        @_annoElem = @annoElem()

        @showOverlay()
        @emphasiseTarget()

        $target.after(@_annoElem) # insert into DOM

        @_annoElem.addClass('anno-target-'+@arrowPositionFn())
        @positionAnnoElem()

        setTimeout (() => @_annoElem.removeClass('anno-hidden')), 10 # hack to make Chrome render the opacity:0 state.

        $target.scrollintoview()
        setTimeout (() => @_annoElem.scrollintoview()) , 300 #TODO fix jumpiness

        lastButton = @_annoElem.find('button').last()
        if @rightArrowClicksLastButton
          lastButton.keydown( (evt) -> if evt.keyCode is 39 then $(this).click()  ) # right arrow
        if @autoFocusLastButton
          lastButton.focus() if $target.find(':focus').length is 0 # don't steal focus from inside target element

        @_returnFromOnShow = @onShow(this, $target, @_annoElem)
        return this

      start: () -> @show() # alias because `tour.start()` sounds nicer than `tour.show()`

      rightArrowClicksLastButton: true
      autoFocusLastButton: true

The `onShow` callback does nothing by default, but can be very useful when overridden (e.g. to register a click
listener on the target element.)  Whatever value you return from the `onShow` function will get passed to the
`onHide` callback.  This can be used to unbind event listeners.

      onShow: (anno, $target, $annoElem) ->

      _returnFromOnShow = null

Hiding is done in two stages so that you can re-use one overlay element for a long chain of Anno's.

      hide: () ->
        @hideAnno()
        @hideOverlay()
        return this

      hideAnno: () ->
        @deemphasiseTarget()

        if @_annoElem?
          @_annoElem.addClass('anno-hidden')
          setTimeout () =>
            @_annoElem?.remove() # this method causes hideAnno to get called twice sometimes -> bad.
            @_annoElem = null
          , 300

          @onHide(this, @targetFn(), @_annoElem, @_returnFromOnShow)
        else
          console.warn "Can't hideAnno() for '#{@target}' when @_annoElem is null.  Did you call hideAnno() twice?"

        return this

      onHide: (anno, $target, $annoElem, returnFromOnShow) ->

`switchTo` displays another Anno and reuses the old overlay.

      switchTo: (otherAnno) ->
        if otherAnno?
          @hideAnno() # TODO: prevent this call to `hideAnno()` if the current Anno isn't shown
          otherAnno.show()
        else
          console.warn "Can't switchTo a null object. Hiding completely instead. "
          @hide() # this recovers from a programmer mistake by removing the overlay too

      switchToChainNext: () -> @switchTo @_chainNext

      switchToChainPrev: () -> @switchTo @_chainPrev

Customizing target
------------------

Specify a `target` jQuery selector to link your annotation to the DOM.

      target: 'h1'

`targetFn()` is used internally to return the first element matching your `target` selector (wrapped as a jQuery object).

      targetFn: () ->
        if typeof @target is 'string'
          r = $(@target).filter(':not(.anno-placeholder)') # .anno-placeholder is a clone to prevent text wrapping
          if r.length is 0 then console.error "Couldn't find Anno.target '#{@target}'."
          if r.length > 1 then console.warn "Anno target '#{@target}' matched #{r.length} elements. Targeting the first one."
          r.first()
        else if @target instanceof jQuery
          if @target.length > 1 then console.warn "Anno jQuery target matched #{@target.length} elements. Targeting the first one."
          return @target.first()
        else if @target instanceof HTMLElement
          $(@target)
        else if typeof @target is 'function'
          @target()
        else
          console.error "Unrecognised Anno.target. Please supply a jQuery selector string, a jQuery "+
              "object, a raw DOM element or a function returning a jQuery element. target:"
          console.error @target

`annoElem()` generates the jQuery object that will be inserted into the DOM.

      annoElem: () -> # TODO: should `annoElem()` encapsulate the re-use of one object?
        @_annoElem = $("""

""")
        @_annoElem.find('.anno-inner').
          append( @contentElem() ).
          append( @buttonsElem() ) # these a jquery elements, not HTML strings.
        return @_annoElem # NB: returning the original pointer each time breaks button click events...

      _annoElem: null

CSS classes can be included, e.g. .anno-width-150, 175, 200, 250 (default 300)

      className: ''

Content
-------

      content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'

      # Override this instead of `content` if you don't know the content in advance
      contentFn: () -> @content

      contentElem: () -> $("
"+@contentFn()+"
") # TODO: evaluate how easy it would be to change Anno content while its displayed.

Semi-transparent overlay and other effects
------------------------------------------

      showOverlay: () ->
        if $('.anno-overlay').length is 0
          $('body').append(e = @overlayElem().addClass 'anno-hidden') # TODO: write about pointer-events: none
          setTimeout (() -> e.removeClass 'anno-hidden'), 10
        else
          $('.anno-overlay').replaceWith @overlayElem()

      overlayElem: () ->
        $("

").
          click( (evt) => @overlayClick.call(this, this, evt) )

      overlayClassName: '' # TODO talk about .anno-hidden
      overlayClick: (anno, evt) -> anno.hide()

      hideOverlay: () ->
        $('.anno-overlay').addClass 'anno-hidden'
        setTimeout (() -> $('.anno-overlay').remove()), 300

      emphasiseTarget: ($target = @targetFn()) ->
        $target.closest(':scrollable').on 'mousewheel', (evt) ->  # TODO: register & remove a specific listener ... would this ruin existing jQuery scroll functions?
          evt.preventDefault()
          evt.stopPropagation()
        @_undoEmphasise.push ($t) -> $t.closest(':scrollable').off('mousewheel')

        if $target.css('position')  is 'static'
          $target.after(@_placeholder = $target.clone().addClass('anno-placeholder')) # ensures that the jquery :first selector in targetFn works.
          @_undoEmphasise.push ($t) => @_placeholder?.remove()
          ((a) => @_undoEmphasise.push ($t) -> $t.css position:a )($target.prop('style').position)
          $target.css( position:'absolute' )

          # if switching to position absolute has caused a dimension collapse, manually set H/W.
          if $target.outerWidth() isnt @_placeholder.outerWidth()
            ((a) => @_undoEmphasise.push ($t) -> $t.css width:a )($target.prop('style').width)
            $target.css('width', @_placeholder.outerWidth())
          if $target.outerHeight() isnt @_placeholder.outerHeight()
            ((a) => @_undoEmphasise.push ($t) -> $t.css height:a )($target.prop('style').height)
            $target.css('height', @_placeholder.outerHeight())

          # if switching to position absolute has caused a position change, manually set it too
          ppos = @_placeholder.position()
          tpos = $target.position()
          if tpos.top  isnt ppos.top
            ((a) => @_undoEmphasise.push ($t) -> $t.css top:a )($target.prop('style').top)
            $target.css('top', ppos.top)
          if tpos.left isnt ppos.left
            ((a) => @_undoEmphasise.push ($t) -> $t.css left:a )($target.prop('style').left)
            $target.css('left', ppos.left)

        if $target.css('background') is 'rgba(0, 0, 0, 0) none repeat scroll 0% 0% / auto padding-box border-box'
          ((a) => @_undoEmphasise.push ($t) -> $t.css background:a )($target.prop('style').background)
          $target.css( background: 'white')

        ((a) => @_undoEmphasise.push ($t) -> $t.css zIndex:a )($target.prop('style').zIndex)
        $target.css( zIndex:'1001' )

        return $target

      _undoEmphasise: [] # a list of functions to undo the effects of emphasiseTarget()
      _placeholder: null

      deemphasiseTarget: () ->
        $target = @targetFn()
        fn($target) for fn in @_undoEmphasise
        return $target

Positioning
-----------

The `position` property decides where your annotation will be displayed.

Alternatively, you can supply a hash of CSS attributes:
```
  position = {
    top: '10px',
    left: '57px'
  }
```
If you omit the `position` attribute entirely, Anno will use its best guess.

      position: null # any of `top`, `left`, `bottom`, `right`, `center-top`, `center-left`, `center-bottom` or `center-right`.

`positionAnnoElem()` sets the CSS of the Anno element so that it appears next to your target in a sensible way.

      positionAnnoElem: (annoEl = @_annoElem) -> # Must be called after DOM insertion.
        pos = @positionFn()

        $targetEl = @targetFn()

        offset = $targetEl.position()
        switch pos
          when 'top', 'bottom'
            annoEl.css(left: offset.left+'px')
          when 'center-top', 'center-bottom'
            annoEl.css(left: offset.left+($targetEl.outerWidth()/2 - annoEl.outerWidth()/2)+'px')
          when 'left', 'right'
            annoEl.css(top: offset.top+'px')
          when 'center-left', 'center-right'
            annoEl.css(top: offset.top+($targetEl.outerHeight()/2 - annoEl.outerHeight()/2)+'px')

        switch pos
          when 'top', 'center-top'
            annoEl.css( top: offset.top-annoEl.outerHeight()+'px')
          when 'bottom', 'center-bottom'
            annoEl.css( top: offset.top+$targetEl.outerHeight()+'px')
          when 'left', 'center-left'
            annoEl.css(left: offset.left-annoEl.outerWidth()+'px')
          when 'right', 'center-right'
            annoEl.css(left: offset.left+$targetEl.outerWidth()+'px')
          else
            if pos.left? or pos.right? or pos.top? or pos.bottom?
             annoEl.css(pos)
            else
              console.error "Unrecognised position: '#{pos}'"

        return annoEl

`positionFn()` returns the `position` property or tries to guess one if you left it blank.

      positionFn: () -> # Must be called after DOM insertion.
        if @position?
          return @position
        else if @_annoElem?
          $target = @targetFn()

          $container = $target.closest(':scrollable')
          $container = $('body') if $container.length is 0

          targetOffset = $target.offset()         # both relative to document
          containerOffset = $container.offset()
          targetBounds =
            left: targetOffset.left - containerOffset.left
            top:  targetOffset.top - containerOffset.top
          targetBounds.right = targetBounds.left + $target.outerWidth() # dist from left edge of $container to right of elem
          targetBounds.bottom = targetBounds.top + $target.outerHeight()

          viewBounds =
            w: $container.width() or $container.width() # TODO: what is this???
            h: $container.height() or $container.height()

          annoBounds =
            w: @_annoElem.outerWidth()
            h: @_annoElem.outerHeight()

          bad = []

          if annoBounds.w > targetBounds.left then bad = bad.concat ['left', 'center-left']
          if annoBounds.h > targetBounds.top  then bad = bad.concat ['top', 'center-top']
          if annoBounds.w + targetBounds.right  > viewBounds.w  then bad = bad.concat ['right', 'center-right']
          if annoBounds.h + targetBounds.bottom > viewBounds.h then bad = bad.concat ['bottom', 'center-bottom']

          allowed = Anno.preferredPositions.filter (p) -> p not in bad
          if allowed.length is 0
            console.error "Anno couldn't guess a position for '#{@target}'. Please supply one in the constructor."
          else
            console.warn "Anno: guessing position:'#{allowed[0]}' for '#{@target}'. "+
              "Possible Anno.preferredPositions: [#{allowed}]."
          return @position = allowed[0] # store this value for later - saves recomputing.

When there are several different positions that the Anno element could by displayed, `positionFn()` chooses
the first one available in `Anno.preferredPositions`.

      @preferredPositions = ['bottom', 'right', 'left', 'top',  # TODO: would it ever guess center-*?
              'center-bottom', 'center-right', 'center-left', 'center-top'] # TODO order these based on research.

`arrowPositionFn()` returns which way the arrow should point. (Normally just the opposite of the anno position.)

      arrowPositionFn: () ->
        if @arrowPosition?
          return @arrowPosition
        else if typeof @positionFn() is 'string'
          return {
            'top': 'bottom'
            'center-top': 'center-bottom'
            'left': 'right'
            'center-left' : 'center-right'
            'right' : 'left'
            'center-right' : 'center-left'
            'bottom': 'top'
            'center-bottom' : 'center-top'
          }[@positionFn()]
        else
          pos = l : parseInt(@positionFn().left, 10) ,  t : parseInt(@positionFn().top, 10)
          if Math.abs(pos.l) > Math.abs(pos.t)
            r = if pos.l < 0 then 'center-right' else 'center-left'
          else
            r = if pos.t < 0 then 'center-bottom' else 'center-top'
          console.warn "Guessing arrowPosition:'#{r}' for #{@target}. Include this in your constructor for consistency."
          return r

Override this if you've supplied a CSS hash as the `position` property. Can be any of `top`, `left`, etc.

      arrowPosition: null # TODO replace 'arrowPosition' with 'arrowDirection'

Customizing Buttons
-------------------

By default, Annotations have a single button, filled with default values from the `AnnoButton` class.

      buttons:  [ {} ]

      # returns a list of `AnnoButton` objects
      buttonsFn: () ->
        if @buttons instanceof Array
          @buttons.map (b) -> new AnnoButton(b)
        else
          [new AnnoButton(@buttons)] # in the else branch `@buttons` is a single hash

`buttonsElem()` produces the HTML for all those buttons (as a jQuery object).

      buttonsElem: () ->
        return $("

").
          append (b.buttonElem(this) for b in @buttonsFn())

AnnoButton
==========

    class AnnoButton

      constructor: (options) ->
        for key,val of options
          this[key]=val

      buttonElem: (anno) ->
        return $("<button class='anno-btn'></button>").
          html( @textFn(anno) ).
          addClass( @className ).
          click( (evt) => @click.call(anno, anno, evt) )

      textFn: (anno) ->
        if @text? then @text
        else if anno._chainNext? then 'Next' else 'Done'

      text: null

      className: ''

`click` is called when your button is clicked.  Note, the `this` keyword is bound to the parent
Anno object.

      click: (anno, evt) ->
        if anno._chainNext?
          anno.switchToChainNext()
        else
          anno.hide()

These are some handy presets that you can use by adding `AnnoButton.NextButton` to your Anno object's
`buttons` list.

      @NextButton: new AnnoButton({ text: 'Next' , click: () -> @switchToChainNext()  })

      @DoneButton: new AnnoButton({ text: 'Done' , click: () -> @hide()  })

      @BackButton: new AnnoButton(
          text: 'Back'
          className: 'anno-btn-low-importance'
          click: () -> @switchToChainPrev()
        )


Add some links, fix doc format

Add some links, fix doc format

# Batman.Model

For a general explanation of `Batman.Model` and how it works, see [the guide](/docs/models.html).

_Note_: This documentation uses the term _model_ to refer to the class `Model`
or a `Model` subclass, and the term _record_ to refer to one instance of a
model.

## @primaryKey : string

`primaryKey` is a class level configuration option to change which key batman.js uses as the primary key. Change the option using `set`, like so:

    test 'primary key can be set using @set', ->
      class Shop extends Batman.Model
        @set 'primaryKey', 'shop_id'

      equal Shop.get('primaryKey'), 'shop_id'

The `primaryKey` is what batman.js uses to compare instances to see if they represent the same domain-level object: if two records have the same value at the key specified by `primaryKey`, only one will be in the identity map. The key specified by `primaryKey` is also used by the associations system when determining if a record is related to another record, and by the remote storage adapters to generate URLs for records.

_Note_: The default primaryKey is 'id'.

## @storageKey : string

`storageKey` is a class level option which gives the storage adapters something to interpolate into their specific key generation schemes. In the case of `LocalStorage` or `SessionStorage` adapters, the `storageKey` defines what namespace to store this record under in the `localStorage` or `sessionStorage` host objects, and with the case of the `RestStorage` family of adapters, the `storageKey` assists in URL generation. See the documentation for the storage adapter of your choice for more information.

The default `storageKey` is `null`.

## @persist(mechanism : StorageAdapter) : StorageAdapter

`@persist` is how a `Model` subclass is told to persist itself by means of a `StorageAdapter`. `@persist` accepts either a `StorageAdapter` class or instance and will return either the instantiated class or the instance passed to it for further modification.

    test 'models can be told to persist via a storage adapter', ->
      class Shop extends Batman.Model
        @resourceName: 'shop'
        @persist TestStorageAdapter

      record = new Shop
      ok record.hasStorage()

    test '@persist returns the instantiated storage adapter', ->
      adapter = false
      class Shop extends Batman.Model
        @resourceName: 'shop'
        adapter = @persist TestStorageAdapter

      ok adapter instanceof Batman.StorageAdapter

    test '@persist accepts already instantiated storage adapters', ->
      adapter = new Batman.StorageAdapter
      adapter.someHandyConfigurationOption = true
      class Shop extends Batman.Model
        @resourceName: 'shop'
        @persist adapter

      record = new Shop
      ok record.hasStorage()

## @encode(keys...[, encoderObject : [Object|Function]])

`@encode` specifies a list of `keys` a model should expect from and send back to a storage adapter, and any transforms to apply to those attributes as they enter and exit the world of batman.js in the optional `encoderObject`.

The `encoderObject` should have an `encode` and/or a `decode` key which point to functions. The functions accept the "raw" data (the batman.js land value in the case of `encode`, and the backend land value in the case of `decode`), and should return the data suitable for the other side of the link. The functions should have the following signatures:

    encoderObject = {
      encode: (value, key, builtJSON, record) ->
      decode: (value, key, incomingJSON, outgoingObject, record) ->
    }

By default these functions are the identity functions. They apply no transformation. The arguments for `encode` functions are as follows:

 + `value` is the client side value of the `key` on the `record`
 + `key` is the key which the `value` is stored under on the `record`. This is useful when passing the same `encoderObject` which needs to pivot on what key is being encoded to different calls to `encode`.
 + `builtJSON` is the object which is modified by each encoder which will eventually be returned by `toJSON`. To send the server the encoded value under a different key than the `key`, modify this object by putting the value under the desired key, and return `undefined`.
 + `record` is the record on which `toJSON` has been called.

For `decode` functions:

 + `value` is the server side value of the `key` which will end up on the `record`.
 + `key` is the key which the `value` is stored under in the incoming JSON.
 + `incomingJSON` is the JSON which is being decoded into the `record`. This can be used to create compound key decoders.
 + `outgoingObject` is the object which is built up by the decoders and then `mixin`'d to the record.
 + `record` is the record on which `fromJSON` has been called.

The `encode` and `decode` keys can also be false to avoid using the default identity function encoder or decoder.

_Note_: `Batman.Model` subclasses have no encoders by default, except for one which automatically decodes the `primaryKey` of the model, which is usually `id`. To get any data into or out of your model, you must white-list the keys you expect from the server or storage attribute.

    test '@encode accepts a list of keys which are used during decoding', ->
      class Shop extends Batman.Model
        @resourceName: 'shop'
        @encode 'name', 'url', 'email', 'country'

      json = {name: "Snowdevil", url: "snowdevil.ca"}
      record = new Shop()
      record.fromJSON(json)
      equal record.get('name'), "Snowdevil"

    test '@encode accepts a list of keys which are used during encoding', ->
      class Shop extends Batman.Model
        @resourceName: 'shop'
        @encode 'name', 'url', 'email', 'country'

      record = new Shop(name: "Snowdevil", url: "snowdevil.ca")
      deepEqual record.toJSON(), {name: "Snowdevil", url: "snowdevil.ca"}

    test '@encode accepts custom encoders', ->
      class Shop extends Batman.Model
        @resourceName: 'shop'
        @encode 'name',
          encode: (name) -> name.toUpperCase()

      record = new Shop(name: "Snowdevil")
      deepEqual record.toJSON(), {name: "SNOWDEVIL"}

    test '@encode accepts custom decoders', ->
      class Shop extends Batman.Model
        @resourceName: 'shop'
        @encode 'name',
          decode: (name) -> name.replace('_', ' ')

      record = new Shop()
      record.fromJSON {name: "Snow_devil"}
      equal record.get('name'), "Snow devil"

    test '@encode can be passed an encoderObject with false to prevent the default encoder or decoder', ->
      class Shop extends Batman.Model
        @resourceName: 'shop'
        @encode 'name', {encode: false, decode: (x) -> x}
        @encode 'url'

      record = new Shop()
      record.fromJSON {name: "Snowdevil", url: "snowdevil.ca"}
      equal record.get('name'), 'Snowdevil'
      equal record.get('url'), "snowdevil.ca"
      deepEqual record.toJSON(), {url: "snowdevil.ca"}, 'The name key is absent because of encode: false'

Some more handy examples:

    test '@encode can be used to turn comma separated values into arrays', ->
      class Post extends Batman.Model
        @resourceName: 'post'
        @encode 'tags',
          decode: (string) -> string.split(', ')
          encode: (array) -> array.join(', ')

      record = new Post()
      record.fromJSON({tags: 'new, hot, cool'})
      deepEqual record.get('tags'), ['new', 'hot', 'cool']
      deepEqual record.toJSON(), {tags: 'new, hot, cool'}

    test '@encode can be used to turn arrays into sets', ->
      class Post extends Batman.Model
        @resourceName: 'post'
        @encode 'tags',
          decode: (array) -> new Batman.Set(array...)
          encode: (set) -> set.toArray()

      record = new Post()
      record.fromJSON({tags: ['new', 'hot', 'cool']})
      ok record.get('tags') instanceof Batman.Set
      deepEqual record.toJSON(), {tags: ['new', 'hot', 'cool']}

## @validate(keys...[, options : [Object|Function]])

Validations allow a model to be marked as `valid` or `invalid` based on a set of programmatic rules. By validating a model's data before it gets to the server we can provide immediate feedback to the user about what they have entered and forgo waiting on a round trip to the server. `validate` allows the attachment of validations to the model on particular keys, where the validation is either a built in one (invoked by use of options to pass to them) or a custom one (invoked by use of a custom function as the second argument).

_Note_: Validation in batman.js is always asynchronous, despite the fact that none of the validations may use an asynchronous operation to check for validity. This is so that the API is consistent regardless of the validations used.

Built in validators are attached by calling `@validate` with options designating how to calculate the validity of the key:

    test '@validate accepts options to check for validity', ->
      QUnit.expect(0)
      class Post extends Batman.Model
        @resourceName: 'post'
        @validate 'title', 'body', {presence: true}

The built in validation options are listed below:

 + `presence : boolean`: Assert that the string value is existent (not undefined or null) and has length greather than 0.
 + `numeric : true`: Assert that the value is or can be coerced into a number using `parseFloat`.
 + `greaterThan : number`: Assert that the value is greater than the given number.
 + `greaterThanOrEqualTo : number`: Assert that the value is greater than or equal to the given number.
 + `equalTo : number`: Assert that the value is equal to the given number.
 + `lessThan : number`: Assert that the value is less than the given number.
 + `lessThanOrEqualTo : number`: Assert that the value is less than or equal to the given number.
 + `minLength : number`: Assert that the value's `length` property is greater than the given number.
 + `maxLength : number`: Assert that the value's `length` property is less than the given number.
 + `length : number`: Assert that the value's `length` property is exactly the given number.
 + `lengthWithin : [number, number]` or `lengthIn : [number, number]`: Assert that the value's `length` property is within the ranger specified by the given array of two numbers, where the first number is the lower bound and the second number is the upper bound.
 + `inclusion : in : [list, of, acceptable, values]`: Assert that the value is equal to one of the values in an array.
 + `exclusion : in : [list, of, unacceptable, values]`: Assert that the value is not equal to any of the values in an array.

Custom validators should have the signature `(errors, record, key, callback)`. The arguments are as follows:

 + `errors`: an `ErrorsSet` instance which expects to have `add` called on it to add errors to the model
 + `record`: the record being validated
 + `key`: the key to which the validation has been attached
 + `callback`: a function to call once validation has been completed. Calling this function is __mandatory__.

See `Model::validate` for information on how to get a particular record's validity.

## @loaded : Set

The `loaded` set is available on every model class and holds every model instance seen by the system in order to function as an identity map. Successfully loading or saving individual records or batches of records will result in those records being added to the `loaded` set. Destroying instances will remove records from the identity set.

    test 'the loaded set stores all records seen', ->
      class Post extends Batman.Model
        @resourceName: 'post'
        @persist TestStorageAdapter
        @encode 'name'

      ok Post.get('loaded') instanceof Batman.Set
      equal Post.get('loaded.length'), 0
      post = new Post()
      post.save()
      equal Post.get('loaded.length'), 1

    test 'the loaded adds new records caused by loads and removes records caused by destroys', ->
      class Post extends Batman.Model
        @resourceName: 'post'
        @encode 'name'

      adapter = new TestStorageAdapter(Post)
      adapter.storage =
          'posts1': {name: "One", id:1}
          'posts2': {name: "Two", id:2}

      Post.persist(adapter)
      Post.load()
      equal Post.get('loaded.length'), 2
      post = false
      Post.find(1, (err, result) -> post = result)
      post.destroy()
      equal Post.get('loaded.length'), 1

## @all : Set

The `all` set is an alias to the `loaded` set but with an added implicit `load` on the model. `Model.get('all')` will synchronously return the `loaded` set and asynchronously call `Model.load()` without options to load a batch of records and populate the set originally returned (the `loaded` set) with the records returned by the server.

_Note_: The notion of "all the records" is relative only to the client. It completely depends on the storage adapter in use and any backends which they may contact to determine what comes back during a `Model.load`. This means that if for example your API paginates records, the set found in `all` may hold on the first 50 records instead of the entire backend set.

`all` is useful for listing every instance of a model in a view, and since the `loaded` set will change when the `load` returns, it can be safely bound to.

    asyncTest 'the all set asynchronously fetches records when gotten', ->
      class Post extends Batman.Model
        @resourceName: 'post'
        @encode 'name'

      adapter = new AsyncTestStorageAdapter(Post)
      adapter.storage =
          'posts1': {name: "One", id:1}
          'posts2': {name: "Two", id:2}

      Post.persist(adapter)
      equal Post.get('all.length'), 0, "The synchronously returned set is empty"
      delay ->
        equal Post.get('all.length'), 2, "After the async load the set is populated"

## @clear() : Set

`Model.clear()` empties that `Model`'s identity map. This is useful for tests and other unnatural situations where records new to the system are guaranteed to be as such.

    test 'clearing a model removes all records from the identity map', ->
      class Post extends Batman.Model
        @resourceName: 'post'
        @encode 'name'

      adapter = new TestStorageAdapter(Post)
      adapter.storage =
          'posts1': {name: "One", id:1}
          'posts2': {name: "Two", id:2}
      Post.persist(adapter)
      Post.load()
      equal Post.get('loaded.length'), 2
      Post.clear()
      equal Post.get('loaded.length'), 0, "After clear() the loaded set is empty"

## @find(id, callback : Function) : Model

`Model.find()` retrieves a record with the specified `id` from the storage adapter and calls back with an error if one occurred and the record if the operation was successful. `find` delegates to the storage adapter the `Model` has been `@persist`ed with, so it is up to the storage adapter's semantics to determine what type of errors may return and the timeline on which the callback may be called. The `callback` is a required function which should adopt the node style callback signature which accepts two arguments: an error, and the record asked for. `find` returns an "unloaded" record which, following the load completion, will be populated with the data from the storage adapter.

_Note_: `find` gives two results to calling code: one immediately, and one later. `find` returns a record synchronously as it is called and calls back with a record, and importantly these two records are __not__ guaranteed to be the same instance. This is because batman.js maps the identities of incoming and outgoing records such that there is only ever one canonical instance representing a record, which is useful so bindings are always bound to the same thing. In practice, this means that calling code should use the record `find` calls back with if anything is going to bind to that object, which is most of the time. The returned record however remains useful for state inspection and bookkeeping.

    asyncTest '@find calls back the requested model if no error occurs', ->
      class Post extends Batman.Model
        @resourceName: 'post'
        @encode 'name'
        @persist AsyncTestStorageAdapter,
          storage:
            'posts2': {name: "Two", id:2}

      post = Post.find 2, (err, result) ->
        throw err if err
        post = result
      equal post.get('name'), undefined
      delay ->
        equal post.get('name'), "Two"

_Note_: `find` must be passed a callback function. This is for two reasons: calling code must be aware that `find`'s return value is not necessarily the canonical instance, and calling code must be able to handle errors.

    asyncTest '@find calls back with the error if an error occurs', ->
      class Post extends Batman.Model
        @resourceName: 'post'
        @encode 'name'
        @persist AsyncTestStorageAdapter

      error = false
      post = Post.find 3, (err, result) ->
        error = err
      delay ->
        ok error instanceof Error

## @load(options = {}, callback : Function)

`Model.load()` retrieves an array of records according to the given `options` from the storage adapter and calls back with an error if one occurred and the set of records if the operation was successful. `load` delegates to the storage adapter the `Model` has been `@persist`ed with, so it is up to the storage adapter's semantics to determine what the options do, what kind of errors may arise, and the timeline on which the callback may be called. The `callback` is a required function which should adopt the node style callback signature which accepts two arguments, an error, and the array of records. `load` returns undefined.

For the two main `StorageAdapter`s batman.js provides, the `options` do different things:

- For `Batman.LocalStorage`, `options` act as a filter. The adapter will scan all the records in `localStorage` and return only those records which match all the key/value pairs given in the options.
- For `Batman.RestStorage`, `options` are serialized into query parameters on the `GET` request.

It takes a callback with two arguments: error and the array of loaded records.

    asyncTest '@load calls back an array of records retrieved from the storage adapter', ->
      class Post extends Batman.Model
        @resourceName: 'post'
        @encode 'name'
        @persist TestStorageAdapter,
          storage:
            'posts1': {name: "One", id:1}
            'posts2': {name: "Two", id:2}

      posts = false
      Post.load (err, result) ->
        throw err if err
        posts = result

      delay ->
        equal posts.length, 2
        equal posts[0].get('name'), "One"

    asyncTest '@load calls back with an empty array if no records are found', ->
      class Post extends Batman.Model
        @resourceName: 'post'
        @encode 'name'
        @persist TestStorageAdapter, storage: []

      posts = false
      Post.load (err, result) ->
        throw err if err
        posts = result

      delay ->
        equal posts.length, 0

## @create(attributes = {}, callback) : Model

## @findOrCreate(attributes = {}, callback) : Model

## id : value

## dirtyKeys : Set

## ::%errors : ErrorsSet

A record's errors are accessible with `get('errors')`, which returns a [`Batman.Set`](/docs/api/batman.set.html) of [`Batman.ValidationError`](/docs/api/batman.validationerror.html)s.

- `user.get('errors')` returns the errors on the `user` record
- `user.get('errors.length')` returns the number of errors, total

You can also access the errors for a specific attribute of the record:

- `user.get('errors.email_address')` returns the errors on the `email_address` attribute
- `user.get('errors.email_address.length')` returns the number of errors on the `email_address` attribute

## constructor(idOrAttributes = {}) : Model

## isNew() : boolean

## updateAttributes(attributes) : Model

## toString() : string

## toJSON() : Object

## fromJSON() : Model

## toParam() : value

## state() : string

## hasStorage() : boolean

## load(options = {}, callback)

## save(options = {}, callback)

## destroy(options = {}, callback)

## ::validate(callback)

`Model::validate` checks the model against the validations declared in the model definition (with [`Model@validate`](/docs/api/batman.model.html#class_function_validate)). This method takes a callback with two arguments: any JavaScript error that occurs and the [`Batman.ErrorsSet`](/docs/api/batman.errorsset.html) resulting from the validation tests.
For example:

    asyncTest "validate(callback) will call the callback only after all keys have been validated", 4, ->
      class Product extends Batman.Model
        @validate 'name', 'price', presence: yes

      new_product = new Product
      new_product.validate (js_error, validation_errors) ->
        throw js_error if js_error
        equal validation_errors.length, 2
        equal new_product.get('errors.length'), 2
        equal new_product.get('errors.name.length'), 1
        equal new_product.get('errors.price.length'), 1

# Batman.ValidationError

A `Batman.ValidationError` is a [`Batman.Object`](/docs/api/batman.object.html). It is initialized with an `attribute` and a `message`.  It also responds to `fullMessage`:

    test "ValidationError should humanize attribute in the full message", ->
      error = new Batman.ValidationError("fooBarBaz", "isn't valid")
      equal error.get('fullMessage'), "Foo bar baz isn't valid"

## ::constructor(attribute_name, message)

Creates a new `Batman.ValidationError`, as in:

```coffeescript
new_error = new Batman.ValidationError("password", "must include letters and numbers")
```

## ::%fullMessage

Returns the attribute name and the validation message.
```coffeescript
new_error.get('fullMessage') # "password must include letters and numbers"
```

# Batman.ErrorsSet

A `Batman.ErrorsSet` is a [`Batman.Set`](/docs/api/batman.set.html) populated with [`Batman.ValidationErrors`](/docs/api/batman.validationerror.html).

- `user.get('errors')` returns the errors on the `user` record
- `user.get('errors.length')` returns the number of errors, total

You can also access the errors for a specific attribute of the record:

- `user.get('errors.email_address')` returns the errors on the `email_address` attribute
- `user.get('errors.email_address.length')` returns the number of errors on the `email_address` attribute

## ::add(attribute_name, message)

`Batman.ErrorsSet::add` is overridden for convenience. It takes an attribute_name and a message, which it uses to
create a new `Batman.ValidationError` then it adds that error to the `Batman.ErrorsSet`.

    test "ErrorsSet::add passes its arguments to the ValidationError constructor", ->
      error_set = new Batman.ErrorsSet()
      error_set.add("Foo bar baz", "isn't valid")
      error = error_set.at(0)
      equal error.get('fullMessage'), "Foo bar baz isn't valid"

# Batman.Model

For a general explanation of `Batman.Model` and it works, see [the guide](/docs/models.html).

_Note_: This documentation uses the term _model_ to refer to the class `Model`
or a `Model` subclass, and the term _record_ to refer to one instance of a
model.

## @primaryKey : string

`primaryKey` is a class level configuration option to change which key batman.js uses as the primary key. Change the option using `set`, like so:

    test 'primary key can be set using @set', ->
      class Shop extends Batman.Model
        @set 'primaryKey', 'shop_id'

      equal Shop.get('primaryKey'), 'shop_id'

The `primaryKey` is what batman.js uses to compare instances to see if they represent the same domain-level object: if two records have the same value at the key specified by `primaryKey`, only one will be in the identity map. The key specified by `primaryKey` is also used by the associations system when determining if a record is related to another record, and by the remote storage adapters to generate URLs for records.

_Note_: The default primaryKey is 'id'.

## @storageKey : string

`storageKey` is a class level option which gives the storage adapters something to interpolate into their specific key generation schemes. In the case of `LocalStorage` or `SessionStorage` adapters, the `storageKey` defines what namespace to store this record under in the `localStorage` or `sessionStorage` host objects, and with the case of the `RestStorage` family of adapters, the `storageKey` assists in URL generation. See the documentation for the storage adapter of your choice for more information.

The default `storageKey` is `null`.

## @persist(mechanism : StorageAdapter) : StorageAdapter

`@persist` is how a `Model` subclass is told to persist itself by means of a `StorageAdapter`. `@persist` accepts either a `StorageAdapter` class or instance and will return either the instantiated class or the instance passed to it for further modification.

    test 'models can be told to persist via a storage adapter', ->
      class Shop extends Batman.Model
        @resourceName: 'shop'
        @persist TestStorageAdapter

      record = new Shop
      ok record.hasStorage()

    test '@persist returns the instantiated storage adapter', ->
      adapter = false
      class Shop extends Batman.Model
        @resourceName: 'shop'
        adapter = @persist TestStorageAdapter

      ok adapter instanceof Batman.StorageAdapter

    test '@persist accepts already instantiated storage adapters', ->
      adapter = new Batman.StorageAdapter
      adapter.someHandyConfigurationOption = true
      class Shop extends Batman.Model
        @resourceName: 'shop'
        @persist adapter

      record = new Shop
      ok record.hasStorage()

## @encode(keys...[, encoderObject : [Object|Function]])

`@encode` specifies a list of `keys` a model should expect from and send back to a storage adapter, and any transforms to apply to those attributes as they enter and exit the world of batman.js in the optional `encoderObject`.

The `encoderObject` should have an `encode` and/or a `decode` key which point to functions. The functions accept the "raw" data (the batman.js land value in the case of `encode`, and the backend land value in the case of `decode`), and should return the data suitable for the other side of the link. The functions should have the following signatures:

    encoderObject = {
      encode: (value, key, builtJSON, record) ->
      decode: (value, key, incomingJSON, outgoingObject, record) ->
    }

By default these functions are the identity functions. They apply no transformation. The arguments for `encode` functions are as follows:

 + `value` is the client side value of the `key` on the `record`
 + `key` is the key which the `value` is stored under on the `record`. This is useful when passing the same `encoderObject` which needs to pivot on what key is being encoded to different calls to `encode`.
 + `builtJSON` is the object which is modified by each encoder which will eventually be returned by `toJSON`. To send the server the encoded value under a different key than the `key`, modify this object by putting the value under the desired key, and return `undefined`.
 + `record` is the record on which `toJSON` has been called.

For `decode` functions:

 + `value` is the server side value of the `key` which will end up on the `record`.
 + `key` is the key which the `value` is stored under in the incoming JSON.
 + `incomingJSON` is the JSON which is being decoded into the `record`. This can be used to create compound key decoders.
 + `outgoingObject` is the object which is built up by the decoders and then `mixin`'d to the record.
 + `record` is the record on which `fromJSON` has been called.

The `encode` and `decode` keys can also be false to avoid using the default identity function encoder or decoder.

_Note_: `Batman.Model` subclasses have no encoders by default, except for one which automatically decodes the `primaryKey` of the model, which is usually `id`. To get any data into or out of your model, you must white-list the keys you expect from the server or storage attribute.

    test '@encode accepts a list of keys which are used during decoding', ->
      class Shop extends Batman.Model
        @resourceName: 'shop'
        @encode 'name', 'url', 'email', 'country'

      json = {name: "Snowdevil", url: "snowdevil.ca"}
      record = new Shop()
      record.fromJSON(json)
      equal record.get('name'), "Snowdevil"

    test '@encode accepts a list of keys which are used during encoding', ->
      class Shop extends Batman.Model
        @resourceName: 'shop'
        @encode 'name', 'url', 'email', 'country'

      record = new Shop(name: "Snowdevil", url: "snowdevil.ca")
      deepEqual record.toJSON(), {name: "Snowdevil", url: "snowdevil.ca"}

    test '@encode accepts custom encoders', ->
      class Shop extends Batman.Model
        @resourceName: 'shop'
        @encode 'name',
          encode: (name) -> name.toUpperCase()

      record = new Shop(name: "Snowdevil")
      deepEqual record.toJSON(), {name: "SNOWDEVIL"}

    test '@encode accepts custom decoders', ->
      class Shop extends Batman.Model
        @resourceName: 'shop'
        @encode 'name',
          decode: (name) -> name.replace('_', ' ')

      record = new Shop()
      record.fromJSON {name: "Snow_devil"}
      equal record.get('name'), "Snow devil"

    test '@encode can be passed an encoderObject with false to prevent the default encoder or decoder', ->
      class Shop extends Batman.Model
        @resourceName: 'shop'
        @encode 'name', {encode: false, decode: (x) -> x}
        @encode 'url'

      record = new Shop()
      record.fromJSON {name: "Snowdevil", url: "snowdevil.ca"}
      equal record.get('name'), 'Snowdevil'
      equal record.get('url'), "snowdevil.ca"
      deepEqual record.toJSON(), {url: "snowdevil.ca"}, 'The name key is absent because of encode: false'

Some more handy examples:

    test '@encode can be used to turn comma separated values into arrays', ->
      class Post extends Batman.Model
        @resourceName: 'post'
        @encode 'tags',
          decode: (string) -> string.split(', ')
          encode: (array) -> array.join(', ')

      record = new Post()
      record.fromJSON({tags: 'new, hot, cool'})
      deepEqual record.get('tags'), ['new', 'hot', 'cool']
      deepEqual record.toJSON(), {tags: 'new, hot, cool'}

    test '@encode can be used to turn arrays into sets', ->
      class Post extends Batman.Model
        @resourceName: 'post'
        @encode 'tags',
          decode: (array) -> new Batman.Set(array...)
          encode: (set) -> set.toArray()

      record = new Post()
      record.fromJSON({tags: ['new', 'hot', 'cool']})
      ok record.get('tags') instanceof Batman.Set
      deepEqual record.toJSON(), {tags: ['new', 'hot', 'cool']}

## @validate(keys...[, options : [Object|Function]])

Validations allow a model to be marked as `valid` or `invalid` based on a set of programmatic rules. By validating a model's data before it gets to the server we can provide immediate feedback to the user about what they have entered and forgo waiting on a round trip to the server. `validate` allows the attachment of validations to the model on particular keys, where the validation is either a built in one (invoked by use of options to pass to them) or a custom one (invoked by use of a custom function as the second argument).

_Note_: Validation in batman.js is always asynchronous, despite the fact that none of the validations may use an asynchronous operation to check for validity. This is so that the API is consistent regardless of the validations used.

Built in validators are attached by calling `@validate` with options designating how to calculate the validity of the key:

    test '@validate accepts options to check for validity', ->
      QUnit.expect(0)
      class Post extends Batman.Model
        @resourceName: 'post'
        @validate 'title', 'body', {presence: true}

The built in validation options are listed below:

 + `presence : boolean`: Assert that the string value is existent (not undefined or null) and has length greather than 0.
 + `numeric : true`: Assert that the value is or can be coerced into a number using `parseFloat`.
 + `greaterThan : number`: Assert that the value is greater than the given number.
 + `greaterThanOrEqualTo : number`: Assert that the value is greater than or equal to the given number.
 + `equalTo : number`: Assert that the value is equal to the given number.
 + `lessThan : number`: Assert that the value is less than the given number.
 + `lessThanOrEqualTo : number`: Assert that the value is less than or equal to the given number.
 + `minLength : number`: Assert that the value's `length` property is greater than the given number.
 + `maxLength : number`: Assert that the value's `length` property is less than the given number.
 + `length : number`: Assert that the value's `length` property is exactly the given number.
 + `lengthWithin : [number, number]` or `lengthIn : [number, number]`: Assert that the value's `length` property is within the ranger specified by the given array of two numbers, where the first number is the lower bound and the second number is the upper bound.
 + `inclusion : in : [list, of, acceptable, values]`: Assert that the value is equal to one of the values in an array.
 + `exclusion : in : [list, of, unacceptable, values]`: Assert that the value is not equal to any of the values in an array.

Custom validators should have the signature `(errors, record, key, callback)`. The arguments are as follows:

 + `errors`: an `ErrorsSet` instance which expects to have `add` called on it to add errors to the model
 + `record`: the record being validated
 + `key`: the key to which the validation has been attached
 + `callback`: a function to call once validation has been completed. Calling this function is __mandatory__.

See `Model::validate` for information on how to get a particular record's validity.

## @loaded : Set

The `loaded` set is available on every model class and holds every model instance seen by the system in order to function as an identity map. Successfully loading or saving individual records or batches of records will result in those records being added to the `loaded` set. Destroying instances will remove records from the identity set.

    test 'the loaded set stores all records seen', ->
      class Post extends Batman.Model
        @resourceName: 'post'
        @persist TestStorageAdapter
        @encode 'name'

      ok Post.get('loaded') instanceof Batman.Set
      equal Post.get('loaded.length'), 0
      post = new Post()
      post.save()
      equal Post.get('loaded.length'), 1

    test 'the loaded adds new records caused by loads and removes records caused by destroys', ->
      class Post extends Batman.Model
        @resourceName: 'post'
        @encode 'name'

      adapter = new TestStorageAdapter(Post)
      adapter.storage =
          'posts1': {name: "One", id:1}
          'posts2': {name: "Two", id:2}

      Post.persist(adapter)
      Post.load()
      equal Post.get('loaded.length'), 2
      post = false
      Post.find(1, (err, result) -> post = result)
      post.destroy()
      equal Post.get('loaded.length'), 1

## @all : Set

The `all` set is an alias to the `loaded` set but with an added implicit `load` on the model. `Model.get('all')` will synchronously return the `loaded` set and asynchronously call `Model.load()` without options to load a batch of records and populate the set originally returned (the `loaded` set) with the records returned by the server.

_Note_: The notion of "all the records" is relative only to the client. It completely depends on the storage adapter in use and any backends which they may contact to determine what comes back during a `Model.load`. This means that if for example your API paginates records, the set found in `all` may hold on the first 50 records instead of the entire backend set.

`all` is useful for listing every instance of a model in a view, and since the `loaded` set will change when the `load` returns, it can be safely bound to.

    asyncTest 'the all set asynchronously fetches records when gotten', ->
      class Post extends Batman.Model
        @resourceName: 'post'
        @encode 'name'

      adapter = new AsyncTestStorageAdapter(Post)
      adapter.storage =
          'posts1': {name: "One", id:1}
          'posts2': {name: "Two", id:2}

      Post.persist(adapter)
      equal Post.get('all.length'), 0, "The synchronously returned set is empty"
      delay ->
        equal Post.get('all.length'), 2, "After the async load the set is populated"

## @clear() : Set

`Model.clear()` empties that `Model`'s identity map. This is useful for tests and other unnatural situations where records new to the system are guaranteed to be as such.

    test 'clearing a model removes all records from the identity map', ->
      class Post extends Batman.Model
        @resourceName: 'post'
        @encode 'name'

      adapter = new TestStorageAdapter(Post)
      adapter.storage =
          'posts1': {name: "One", id:1}
          'posts2': {name: "Two", id:2}
      Post.persist(adapter)
      Post.load()
      equal Post.get('loaded.length'), 2
      Post.clear()
      equal Post.get('loaded.length'), 0, "After clear() the loaded set is empty"

## @find(id, callback : Function) : Model

`Model.find()` retrieves a record with the specified `id` from the storage adapter and calls back with an error if one occurred and the record if the operation was successful. `find` delegates to the storage adapter the `Model` has been `@persist`ed with, so it is up to the storage adapter's semantics to determine what type of errors may return and the timeline on which the callback may be called. The `callback` is a required function which should adopt the node style callback signature which accepts two arguments: an error, and the record asked for. `find` returns an "unloaded" record which, following the load completion, will be populated with the data from the storage adapter.

_Note_: `find` gives two results to calling code: one immediately, and one later. `find` returns a record synchronously as it is called and calls back with a record, and importantly these two records are __not__ guaranteed to be the same instance. This is because batman.js maps the identities of incoming and outgoing records such that there is only ever one canonical instance representing a record, which is useful so bindings are always bound to the same thing. In practice, this means that calling code should use the record `find` calls back with if anything is going to bind to that object, which is most of the time. The returned record however remains useful for state inspection and bookkeeping.

    asyncTest '@find calls back the requested model if no error occurs', ->
      class Post extends Batman.Model
        @resourceName: 'post'
        @encode 'name'
        @persist AsyncTestStorageAdapter,
          storage:
            'posts2': {name: "Two", id:2}

      post = Post.find 2, (err, result) ->
        throw err if err
        post = result
      equal post.get('name'), undefined
      delay ->
        equal post.get('name'), "Two"

_Note_: `find` must be passed a callback function. This is for two reasons: calling code must be aware that `find`'s return value is not necessarily the canonical instance, and calling code must be able to handle errors.

    asyncTest '@find calls back with the error if an error occurs', ->
      class Post extends Batman.Model
        @resourceName: 'post'
        @encode 'name'
        @persist AsyncTestStorageAdapter

      error = false
      post = Post.find 3, (err, result) ->
        error = err
      delay ->
        ok error instanceof Error

## @load(options = {}, callback : Function)

`Model.load()` retrieves an array of records according to the given `options` from the storage adapter and calls back with an error if one occurred and the set of records if the operation was successful. `load` delegates to the storage adapter the `Model` has been `@persist`ed with, so it is up to the storage adapter's semantics to determine what the options do, what kind of errors may arise, and the timeline on which the callback may be called. The `callback` is a required function which should adopt the node style callback signature which accepts two arguments, an error, and the array of records. `load` returns undefined.

For the two main `StorageAdapter`s batman.js provides, the `options` do different things:

- For `Batman.LocalStorage`, `options` act as a filter. The adapter will scan all the records in `localStorage` and return only those records which match all the key/value pairs given in the options.
- For `Batman.RestStorage`, `options` are serialized into query parameters on the `GET` request.

It takes a callback with two arguments: error and the array of loaded records.

    asyncTest '@load calls back an array of records retrieved from the storage adapter', ->
      class Post extends Batman.Model
        @resourceName: 'post'
        @encode 'name'
        @persist TestStorageAdapter,
          storage:
            'posts1': {name: "One", id:1}
            'posts2': {name: "Two", id:2}

      posts = false
      Post.load (err, result) ->
        throw err if err
        posts = result

      delay ->
        equal posts.length, 2
        equal posts[0].get('name'), "One"

    asyncTest '@load calls back with an empty array if no records are found', ->
      class Post extends Batman.Model
        @resourceName: 'post'
        @encode 'name'
        @persist TestStorageAdapter, storage: []

      posts = false
      Post.load (err, result) ->
        throw err if err
        posts = result

      delay ->
        equal posts.length, 0

## @create(attributes = {}, callback) : Model

## @findOrCreate(attributes = {}, callback) : Model

## id : value

## dirtyKeys : Set

## errors : ErrorsSet

A record's errors are accessible with `get('errors')`, which returns a `Batman.Set` of `Batman.ValidationError`s.

- `user.get('errors')` returns the errors on the `user` record
- `user.get('errors.length')` returns the number of errors, total

You can also access the errors for a specific attribute of the record:

- `user.get('errors.email_address')` returns the errors on the `email_address` attribute
- `user.get('errors.email_address.length')` returns the number of errors on the `email_address` attribute

## constructor(idOrAttributes = {}) : Model

## isNew() : boolean

## updateAttributes(attributes) : Model

## toString() : string

## toJSON() : Object

## fromJSON() : Model

## toParam() : value

## state() : string

## hasStorage() : boolean

## load(options = {}, callback)

## save(options = {}, callback)

## destroy(options = {}, callback)

## validate(callback)

`Model::validate` checks the model against the validations declared in the model definition (see `Model@validate` above). This method takes a callback with two arguments: any JavaScript error that occurs, then the `Batman.ErrorsSet` corresponding to the validation action.
For example:

    asyncTest "validate(callback) will call the callback only after all keys have been validated", 4, ->
      class Product extends Batman.Model
        @validate 'name', 'price', presence: yes

      p = new Product
      p.validate (js_error, validation_errors) ->
        throw js_error if js_error
        equal validation_errors.length, 2
        equal p.get('errors.length'), 2
        equal p.get('errors.name.length'), 1
        equal p.get('errors.price.length'), 1

# Batman.ValidationError

A `Batman.ValidationError` is a `Batman.Object`. It is initialized with an `attribute` and a `message`.  It also responds to `fullMessage`:

    test "ValidationError should humanize attribute in the full message", ->
      error = new Batman.ValidationError("fooBarBaz", "isn't valid")
      equal error.get('fullMessage'), "Foo bar baz isn't valid"

# Batman.ErrorsSet

A `Batman.ErrorsSet` is a `Batman.Set` populated with `Batman.ValidationErrors`.

- `user.get('errors')` returns the errors on the `user` record
- `user.get('errors.length')` returns the number of errors, total

You can also access the errors for a specific attribute of the record:

- `user.get('errors.email_address')` returns the errors on the `email_address` attribute
- `user.get('errors.email_address.length')` returns the number of errors on the `email_address` attribute

`Batman.ErrorsSet::add` is overridden for convenience. It takes an attribute name and a message, which it uses to
create a new `Batman.ValidationError` then it adds that error to the `Batman.ErrorsSet`.