0.0.16 Tidy tetracoords.litcoffee (2)

0.0.16 Tidy tetracoords.litcoffee (2)

tetraCoords()
=============

#### Calculates the coordinates of a tetrahedron’s vertices from 6 edge lengths

- __G__ The point on the tabletop furthest away from you
- __H__ The leftmost point touching the tabletop
- __I__ The rightmost point touching the tabletop
- __J__ The point in the middle, not touching the table

To watch and compile automatically:
```bash
coffee --watch --compile supplemental/tetracoords/tetracoords.litcoffee
```

#### `tetraCoords()`
- `a <number>`  the edge joining __G — H__
- `b <number>`  the edge joining __H — I__
- `c <number>`  the edge joining __I — G__
- `d <number>`  the edge joining __G — J__
- `e <number>`  the edge joining __J — H__
- `f <number>`  the edge joining __J — I__
- `<array>`     twelve numbers, representing (x,y,z) coords of __G, H, I and J__

    window.tetraCoords = (a, b, c, d, e, f) ->
      M = 'tetraCoords()\n  '

Run basic validation of the six arguments.

      for name,arg of { a:a,b:b,c:c,d:d,e:e,f:f }
        if 'number' != typeof arg then throw TypeError "
          #{M}Argument `#{name}` is #{typeof arg} not number"
        if 9007199254740991 < arg or 0 > arg then throw RangeError "
          #{M}Argument `#{name}` is #{arg} not 0 or a +ve number < 2^53"

Initialize the output array, and place __G,__ (the point on the tabletop
furthest away from you) at the origin.

      out = [
        0 # [0] = Gx
        0 # [1] = Gy
        0 # [2] = Gz
      ]

Use the [law of cosines](https://goo.gl/8djIju) to find the angle (in radians)
of the corner opposite `b` on the tabletop. `cos B = (a² + c² − b²) / 2ac`

      oppB = Math.acos( (a*a + c*c - b*b) / (2*a*c) )

Calculate __H,__ the leftmost point touching the tabletop.

      out.push(
        Math.sin(oppB/2) * -a, # [3] = Hx
        0,                     # [4] = Hy
        Math.cos(oppB/2) * -a  # [5] = Hz
      )

Calculate __I,__ the rightmost point touching the tabletop.

      out.push(
        Math.sin(oppB/2) * c,  # [6] = Ix
        0,                     # [7] = Iy
        Math.cos(oppB/2) * -c  # [8] = Iz
      )

Use `trilaterate()` to calculate __J,__ the raised point in the middle.

      result = trilaterate(
        { x:0     , y:0, z:0     , r:d }, # p1
        { x:out[3], y:0, z:out[5], r:e }, # p2
        { x:out[6], y:0, z:out[8], r:f }  # p3
      )

Deal with an impossible tetrahedron.

      if null == result then throw RangeError "
        #{M}`#{a},#{b},#{c},#{d},#{e},#{f}` cannot be tetrahedron edges"

`trilaterate()` usually returns coords for __J__ above _and_ below the tabletop.

      if 2 == result.length
        out.push(
          result[1].x, #  [9] = Jx
          result[1].y, # [10] = Jy (`result[1]` is always +ve)
          result[1].z  # [11] = Jz
        )

If `trilaterate()` returns an object, we have a ‘flat’ tetrahedron

      else
        out.push(
          result.x,    #  [9] = Jx
          result.y,    # [10] = Jy (always 0)
          result.z     # [11] = Jz
        )

Move the tetrahedron coords so that the origin is roughly in the center.

      dy = -out[10] / 3 # third of height, `-Jy / 3`
      out[y] += dy for y in [1,4,7,10]
      dz = -(out[5] + out[8]) / 4 # half of average depth, `(Hz + Iz) / 2 / 2`
      out[z] += dz for z in [2,5,8,11]

Round results to 3 decimal places.

      out = (Math.round(coord * 1000) / 1000 for coord in out)

Convert to a neat string. @todo pass tetraCoords()` an argument to enable this

      out = (for coord in out
        coord  = (if 0 <= coord then ' ' else '') + coord
        pos = coord.indexOf '.'
        coord += (if -1 == pos then '.000' else '00')
        pos = coord.indexOf '.'
        coord = coord.slice 0, pos + 4
      )

Return the output array.

      out


Strengthen URI component decode

Strengthen URI component decode

    MongoQS     = require 'mongo-querystring'
    Document    = require './model/Document'
    stringify   = require('JSONStream').stringify

    sentry      = require './db/sentry'
    mongo       = require './db/mongo'

    collections = [
      'turer'
      'steder'
      'grupper'
      'områder'
      'bilder'
      'arrangementer'
    ]

    qs = new MongoQS
      alias:
        tag: 'tags.0'
        gruppe: 'grupper'
        endret: 'after'
      ignore:
        api_key     : true # other use
        order       : true # reserved
        sort        : true # other use
        limit       : true # other use
        skip        : true # other use
        fields      : true # other use
        _id         : true # use API endpoint
      custom:
        bbox: 'geojson'
        near: 'geojson'
        after: 'endret'

## PARAM {collection}

    exports.param = (req, res, next, col) ->
      col = decodeURIComponent col

      if col not in collections
        return res.json 404, message: "Type #{col} not found"

      req.type = col
      req.db   = col: mongo[col], query: {}

      next()

    exports.paramCol2 = (req, res, next, col2) ->
      if col not in collections
        return res.json 404, message: "Type #{col2} not found"

      req.type      = col2
      req.db.col    = mongo[col2]
      req.db.query  = [{status: 'Offentlig'}, {tilbyder: req.user.tilbyder}]

      next()

## OPTIONS /{collection}

    exports.options = (req, res, next) ->
      res.setHeader 'Access-Control-Expose-Headers', [
        'ETag', 'Location', 'Last-Modified', 'Count-Return', 'Count-Total'
      ].join(', ')
      res.setHeader 'Access-Control-Max-Age', 86400
      res.setHeader 'Access-Control-Allow-Headers', 'Content-Type'
      res.setHeader 'Access-Control-Allow-Methods', 'HEAD, GET, POST'
      res.send 204

## HEAD /{collection}
## GET /{collection}

    exports.get = (req, res, next) ->

### Query

      req.db.query = qs.parse req.query

Prevent private documents for other API user from being returned when quering
`tilbyder` and `status` fields.

      for key, val of req.query
        switch key
          when 'status'
            req.db.query.tilbyder = req.user.tilbyder if val not in ['Offentlig', 'Slettet']
            break
          when 'tilbyder'
            req.db.query.status = 'Offentlig' if val isnt req.user.tilbyder
            break
          else
            if key.substr(0,6) is 'privat'
              req.db.query.tilbyder = req.user.tilbyder
              break

Apply default access control unless `status` or `tilbyder` fields are already
queried.

      if not req.db.query.tilbyder or req.db.query.status
        req.db.query.$or = [{status: 'Offentlig'}, {tilbyder: req.user.tilbyder}]

### Fields

Always return `lisens` and `tilbyder` fields for propper attribution. Document
ObjectID is always returned by MongoDB.

      fields = lisens: true, tilbyder: true

Parse user specified fields to be returned.

      if typeof req.query.fields is 'string' and req.query.fields
        for field in req.query.fields.split ',' when field.substr(0,6) isnt 'privat'
          fields[field] = true

Default fields if unless specified by the user.

      else
        fields.endret = true
        fields.status = true
        fields.navn = true
        fields.tags = true

### Sort

Limit sort to ascending or descending on `endret` and `navn` since they are
indexed, non-indexed fields could be slower.

      if typeof req.query.sort is 'string' and req.query.sort
        sort = switch req.query.sort
          when 'endret' then [['endret', 1]]
          when '-endret' then [['endret', -1]]
          when 'navn' then [['navn', 1]]
          when '-navn' then [['navn', -1]]
          else 'endret'

Only apply default sort if there are no geospatial queries.

      else
        sort = 'endret' if not req.db.query.geojson

### Execute

Set limit, skip and, sort options for documents to be returned.

      options =
        limit: Math.min((parseInt(req.query.limit, 10) or 20), 50)
        skip: parseInt(req.query.skip, 10) or 0
        sort: sort

      cursor = req.db.col.find(req.db.query, fields, options)

Count number of matching documents in MongoDB database.

      cursor.count false, (err, total) ->
        return next err if err

Calculate the total number of documents that will eventually be returned (not
the total number of matching documents) since we don't know that in advanced
(due to the nature of streaming).

        count = Math.min(options.limit, Math.max(total - options.skip, 0))

Set `Count-Return` and `Count-Total` headers so that one can use a `HEAD` query
to look up the number of matched documents for a query without the documents
them selves. This may be useful for statistics purposes.

        res.set 'Count-Return', count
        res.set 'Count-Total', total

Return to the user if this is a `HEAD` query or there are no matching documents.

        return res.send 204 if req.method is 'HEAD'
        return res.json documents: [], count: 0, total: 0 if total is 0

Stream matching documents in order to prevent loading them into memory.

        res.set 'Content-Type', 'application/json; charset=utf-8'

        op = '{"documents":['
        cl = '],"count":' + count + ',"total":' + total + '}'

        cursor.stream().pipe(stringify(op, ',', cl)).pipe(res)

## POST /{collection}

    exports.post = (req, res, next) ->
      return res.json 400, message: 'Body is missing' if Object.keys(req.body).length is 0
      return res.json 422, message: 'Body should be a JSON Hash' if req.body instanceof Array

      req.body.tilbyder = req.user.tilbyder

      new Document(req.type, null).once('error', next).once 'ready', ->
        @insert req.body, (err, warn, data) ->
          if err
            return next(err) if err.name isnt 'ValidationError'

            sentry.captureDocumentError req, err

            return res.json 422,
              document: req.body
              message: 'Validation Failed'
              errors: err.details #TODO(starefossen) document this

          res.set 'ETag', "\"#{data.checksum}\""
          res.set 'Last-Modified', new Date(data.endret).toUTCString()
          # res.set 'Location', req.get 'host'

          return res.json 201,
            document: data
            message: 'Validation Warnings' if warn.length > 0
            warnings: warn if warn.length > 0

    MongoQS     = require 'mongo-querystring'
    Document    = require './model/Document'
    stringify   = require('JSONStream').stringify

    sentry      = require './db/sentry'
    mongo       = require './db/mongo'

    collections = [
      'turer'
      'steder'
      'grupper'
      'områder'
      'bilder'
      'arrangementer'
    ]

    qs = new MongoQS
      alias:
        tag: 'tags.0'
        gruppe: 'grupper'
        endret: 'after'
      ignore:
        api_key     : true # other use
        order       : true # reserved
        sort        : true # other use
        limit       : true # other use
        skip        : true # other use
        fields      : true # other use
        _id         : true # use API endpoint
      custom:
        bbox: 'geojson'
        near: 'geojson'
        after: 'endret'

## PARAM {collection}

    exports.param = (req, res, next, col) ->
      if col not in collections
        return res.json 404, message: "Type #{col} not found"

      req.type = decodeURIComponent col
      req.db   = col: mongo[req.type], query: {}

      next()

    exports.paramCol2 = (req, res, next, col2) ->
      if col not in collections
        return res.json 404, message: "Type #{col2} not found"

      req.type      = col2
      req.db.col    = mongo[col2]
      req.db.query  = [{status: 'Offentlig'}, {tilbyder: req.user.tilbyder}]

      next()

## OPTIONS /{collection}

    exports.options = (req, res, next) ->
      res.setHeader 'Access-Control-Expose-Headers', [
        'ETag', 'Location', 'Last-Modified', 'Count-Return', 'Count-Total'
      ].join(', ')
      res.setHeader 'Access-Control-Max-Age', 86400
      res.setHeader 'Access-Control-Allow-Headers', 'Content-Type'
      res.setHeader 'Access-Control-Allow-Methods', 'HEAD, GET, POST'
      res.send 204

## HEAD /{collection}
## GET /{collection}

    exports.get = (req, res, next) ->

### Query

      req.db.query = qs.parse req.query

Prevent private documents for other API user from being returned when quering
`tilbyder` and `status` fields.

      for key, val of req.query
        switch key
          when 'status'
            req.db.query.tilbyder = req.user.tilbyder if val not in ['Offentlig', 'Slettet']
            break
          when 'tilbyder'
            req.db.query.status = 'Offentlig' if val isnt req.user.tilbyder
            break
          else
            if key.substr(0,6) is 'privat'
              req.db.query.tilbyder = req.user.tilbyder
              break

Apply default access control unless `status` or `tilbyder` fields are already
queried.

      if not req.db.query.tilbyder or req.db.query.status
        req.db.query.$or = [{status: 'Offentlig'}, {tilbyder: req.user.tilbyder}]

### Fields

Always return `lisens` and `tilbyder` fields for propper attribution. Document
ObjectID is always returned by MongoDB.

      fields = lisens: true, tilbyder: true

Parse user specified fields to be returned.

      if typeof req.query.fields is 'string' and req.query.fields
        for field in req.query.fields.split ',' when field.substr(0,6) isnt 'privat'
          fields[field] = true

Default fields if unless specified by the user.

      else
        fields.endret = true
        fields.status = true
        fields.navn = true
        fields.tags = true

### Sort

Limit sort to ascending or descending on `endret` and `navn` since they are
indexed, non-indexed fields could be slower.

      if typeof req.query.sort is 'string' and req.query.sort
        sort = switch req.query.sort
          when 'endret' then [['endret', 1]]
          when '-endret' then [['endret', -1]]
          when 'navn' then [['navn', 1]]
          when '-navn' then [['navn', -1]]
          else 'endret'

Only apply default sort if there are no geospatial queries.

      else
        sort = 'endret' if not req.db.query.geojson

### Execute

Set limit, skip and, sort options for documents to be returned.

      options =
        limit: Math.min((parseInt(req.query.limit, 10) or 20), 50)
        skip: parseInt(req.query.skip, 10) or 0
        sort: sort

      cursor = req.db.col.find(req.db.query, fields, options)

Count number of matching documents in MongoDB database.

      cursor.count false, (err, total) ->
        return next err if err

Calculate the total number of documents that will eventually be returned (not
the total number of matching documents) since we don't know that in advanced
(due to the nature of streaming).

        count = Math.min(options.limit, Math.max(total - options.skip, 0))

Set `Count-Return` and `Count-Total` headers so that one can use a `HEAD` query
to look up the number of matched documents for a query without the documents
them selves. This may be useful for statistics purposes.

        res.set 'Count-Return', count
        res.set 'Count-Total', total

Return to the user if this is a `HEAD` query or there are no matching documents.

        return res.send 204 if req.method is 'HEAD'
        return res.json documents: [], count: 0, total: 0 if total is 0

Stream matching documents in order to prevent loading them into memory.

        res.set 'Content-Type', 'application/json; charset=utf-8'

        op = '{"documents":['
        cl = '],"count":' + count + ',"total":' + total + '}'

        cursor.stream().pipe(stringify(op, ',', cl)).pipe(res)

## POST /{collection}

    exports.post = (req, res, next) ->
      return res.json 400, message: 'Body is missing' if Object.keys(req.body).length is 0
      return res.json 422, message: 'Body should be a JSON Hash' if req.body instanceof Array

      req.body.tilbyder = req.user.tilbyder

      new Document(req.type, null).once('error', next).once 'ready', ->
        @insert req.body, (err, warn, data) ->
          if err
            return next(err) if err.name isnt 'ValidationError'

            sentry.captureDocumentError req, err

            return res.json 422,
              document: req.body
              message: 'Validation Failed'
              errors: err.details #TODO(starefossen) document this

          res.set 'ETag', "\"#{data.checksum}\""
          res.set 'Last-Modified', new Date(data.endret).toUTCString()
          # res.set 'Location', req.get 'host'

          return res.json 201,
            document: data
            message: 'Validation Warnings' if warn.length > 0
            warnings: warn if warn.length > 0


Refactor be.bastelstu.wcf.nodePush a bit

nodePush - Frontend
===================

This is the frontend JavaScript for [**nodePush**](https://github.com/wbbaddons/nodePush). It transparently handles
everything that has to be done in order to connect to **nodePush** and provides a nice API for 3rd party developers.

	### Copyright Information
	# @author	Tim Düsterhus
	# @copyright	2012-2013 Tim Düsterhus
	# @license	BSD 3-Clause License <http://opensource.org/licenses/BSD-3-Clause>
	# @package	be.bastelstu.wcf.nodePush
	###

## Code
We start by setting up our environment by ensuring some sane values for both `$` and `window`,
enabling EMCAScript 5 strict mode and overwriting console to prepend the name of the class.

	(($, window) ->
		"use strict";

		console =
			log: (message) ->
				window.console.log "[be.bastelstu.wcf.nodePush] #{message}"
			warn: (message) ->
				window.console.warn "[be.bastelstu.wcf.nodePush] #{message}"
			error: (message) ->
				window.console.error "[be.bastelstu.wcf.nodePush] #{message}"

Continue with defining the needed variables. All variables are local to our closure and will be
exposed by a function if necessary.

		socket = null
		connected = false
		initialized = false
		events =
			connect: $.Callbacks()
			disconnect: $.Callbacks()
			message: { }

Initialize socket.io to enable nodePush.

		init = (host) ->
			return if initialized
			initialized = true
			console.log 'Initializing nodePush'

			unless window.io?
				console.error 'nodePush not available, aborting'
				return

			socket = window.io.connect host

			socket.on 'connect', ->
				console.log 'Connected to nodePush'
				socket.emit 'userID', WCF.User.userID

			socket.on 'authenticated', ->
				console.log 'Exchanged userID with nodePush'
				connected = true
				events.connect.fire()

			socket.on 'disconnect', ->
				connected = false
				console.warn 'Lost connection to nodePush'
				events.disconnect.fire()

			socket.on 'message', (message) ->
				return unless events.message[message]?

				events.message[message].fire()

Add a new `callback` that will be called when a connection to nodePush is established and the
userID was exchanged. The given `callback` will be called once if a connection is established at
time of calling. Return `true` on success and `false` otherwise.

		onConnect = (callback) ->
			return false unless $.isFunction callback

			events.connect.add callback

			callback() if connected
			true

Add a new `callback` that will be called when the connection to nodePush is lost. Return `true`
on success and `false` otherwise.

		onDisconnect = (callback) ->
			return false unless $.isFunction callback

			events.disconnect.add callback
			true

Add a new `callback` that will be called when the specified `message` is received. Return `true`
on success and `false` otherwise.

		onMessage = (message, callback) ->
			return false unless $.isFunction callback
			return false unless /^[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+(\.[a-zA-Z0-9-_]+)+$/.test message

			events.message[message] ?= $.Callbacks()
			events.message[message].add callback
			true

And finally export the public methods and variables.

		window.be ?= {}
		be.bastelstu ?= {}
		be.bastelstu.wcf ?= {}
		be.bastelstu.wcf.nodePush =
			init: init
			onConnect: onConnect
			onDisconnect: onDisconnect
			onMessage: onMessage

	)(jQuery, @)

Frontend JavaScript for nodePush
================================

	### Copyright Information
	# @author	Tim Düsterhus
	# @copyright	2012-2013 Tim Düsterhus
	# @license	BSD 3-Clause License <http://opensource.org/licenses/BSD-3-Clause>
	# @package	be.bastelstu.wcf.nodePush
	###

Setup
-----
Ensure sane values for `$` and `window`

	(($, window) ->
		# Enable strict mode
		"use strict";

		# Ensure our namespace is present
		window.be ?= {}
		be.bastelstu ?= {}
		be.bastelstu.wcf ?= {}

Overwrite `console` to add the origin in front of the message

		console =
			log: (message) ->
				window.console.log "[be.bastelstu.wcf.nodePush] #{message}"
			warn: (message) ->
				window.console.warn "[be.bastelstu.wcf.nodePush] #{message}"
			error: (message) ->
				window.console.error "[be.bastelstu.wcf.nodePush] #{message}"

be.bastelstu.wcf.nodePush
=========================

Private Attributes
------------------

		socket = null
		connected = false
		initialized = false
		events =
			connect: $.Callbacks()
			disconnect: $.Callbacks()
			message: { }

		be.bastelstu.wcf.nodePush =

Methods
-------

**init(string host)**
Initialize socket.io to enable nodePush.

			init: (host) ->
				return if initialized
				initialized = true
				console.log 'Initializing nodePush'

				unless window.io?
					console.error 'nodePush not available, aborting'
					return

				socket = window.io.connect host

				socket.on 'connect', ->
					console.log 'Connected to nodePush'
					socket.emit 'userID', WCF.User.userID

				socket.on 'authenticated', ->
					console.log 'Exchanged userID with nodePush'
					connected = true
					events.connect.fire()

				socket.on 'disconnect', ->
					connected = false
					console.warn 'Lost connection to nodePush'
					events.disconnect.fire()

				socket.on 'message', (message) ->
					return unless events.message[message]?

					events.message[message].fire()

**boolean onConnect(Function callback)**
Adds a new `callback` that will be called when a connection to nodePush is established and the
userID was exchanged. The given `callback` will be called once if a connection is established at
time of calling. Returns `true` on success and `false` otherwise.

			onConnect: (callback) ->
				return false unless $.isFunction callback

				events.connect.add callback

				callback() if connected
				true

**boolean onDisconnect(Function callback)**
Adds a new `callback` that will be called when the connection to nodePush is lost. Returns `true`
on success and `false` otherwise.

			onDisconnect: (callback) ->
				return false unless $.isFunction callback

				events.disconnect.add callback
				true

**boolean onMessage(string message, Function callback)**
Adds a new `callback` that will be called when the specified `message` is received. Returns `true`
on success and `false` otherwise.

			onMessage: (message, callback) ->
				return false unless $.isFunction callback
				return false unless /^[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+(\.[a-zA-Z0-9-_]+)+$/.test message

				events.message[message] ?= $.Callbacks()
				events.message[message].add callback
				true

	)(jQuery, @)


reindented lists

reindented lists

# CLASS: Element
* Extends: [Evented][Evented]
* Parent: [Index][Parent]

A visual element. Manages **mousemove**, **mousedown** and **mouseup** events.
Manages **regions** and **local event coordinates**.

---

    module.exports = class Element extends require 'ecl/dist/evented'

## CONSTRUCTOR

### new Element(args)

Creates an Element instance.

      constructor: ->
        super

Copy the element origin if it was set from named arguments

        if @origin then @origin = x:(@origin.x or 0), y:(@origin.y or 0)

Copy the element regions if they were set from named arguments

        if regions = @regions
          @regions = {}
          @regions[name] = region for name, region of regions

Register validating listeners for each **type** from the list.

        for type in ['mousemove', 'mousedown', 'mouseup']

Localize the **type** variable.

          do (type) =>

Add capturing listener which will cancel and stop the event
if the event type is not enabled.

            @addListener type:type, capture:yes, listener: (event) ->
              event.cancel().stop() unless @events?[type]

Add normal listener which will cancel and stop the event
if the event type is not enabled.

            @addListener type:type, capture:no, listener: (event) ->
              event.cancel().stop() unless @events?[type]

Register extendable listeners for each **type** from the list.

        for type in ['mousemove', 'mousedown', 'mouseup']

Add capturing extendable listener

          @addListener type, this["#{type}CaptureListener"], yes

Add normal extendable listener

          @addListener type, this["#{type}Listener"], no

#### PARAMETERS
* **args**
    * Type: [Object][Object] - named arguments

* **args.origin**
    * Type: [Object][Object] - origin coordinates

* **args.origin.x**
    * Type: [Number][Number] - x coordinate

* **args.origin.y**
    * Type: [Number][Number] - y coordinate

* **args.regions** - named regions
    * Type: [Object][Object]

* **args.regions.{NAME}**
    * Type: [Array][Array] - [x, y, width, height]

---

## PROPERTIES

### #origin
* Type: [Object][Object] - origin coordinates
* **x**
  * Type: [Number][Number] - x coordinate
* **y**
  * Type: [Number][Number] - y coordinate

### #regions
* Type: [Object][Object] - region name/value pairs
* **{REGION NAME}**
    * Type: [Array][Array] - [x, y, width, height]

---

## METHODS
### #localizeEventCoordinates(event) ->
* Returns: [Element][Element]

Helper method, to localize event coordinates.

      localizeEventCoordinates: (event) ->
        if event

If event already has localized **x**, use it instead of absolute **x**

          x = (if event.localX? then event.localX else event.x)
          event.localX = x - (@origin?.x or 0)

If event already has localized **y**, use it instead of absolute **y**

          y = (if event.localY? then event.localY else event.y)
          event.localY = y - (@origin?.y or 0)

        return this
---

### #getEventRegions(event)
* Returns: [Object][Object] - named set of matching regions

Helper method, to get element regions that match the event.

      getEventRegions: (event) ->
        result = null

Proceed only if event coordinates are localized

        if event and (x = event.localX)? and (y = event.localY)?

Walk through the element regions

          for name, $ of @regions

and add the region to the result if event localized coordinates are within
the region rectangle.

            if $[0] <= x <= ($[0] + $[2]) and $[1] <= y <= ($[1] + $[3])

Initialize result only if at least one region matches

              result or= {}
              result[name] = $

        return result

---

## LISTENERS
### #mousemoveCaptureListener(event)
* Returns: [Element][Element]

Extendable capturing **mousemove** listener

      mousemoveCaptureListener: (event) ->

Store the event in the runtime for later and localize event coordinates

        @___runtime.mousemoveEvent = event
        @localizeEventCoordinates event

        return this

---

### #mousedownCaptureListener(event)
* Returns: [Element][Element]

Extendable capturing **mousedown** listener

      mousedownCaptureListener: (event) ->

Store the event in the runtime for later and localize event coordinates

        @___runtime.mousedownEvent = event
        @localizeEventCoordinates event

        return this

---

### #mouseupCaptureListener(event)
* Returns: [Element][Element]

Extendable capturing **mouseup** listener

      mouseupCaptureListener: (event) ->

Store the event in the runtime for later and localize event coordinates

        @___runtime.mouseupEvent = event
        @localizeEventCoordinates event

        return this

---

### #mousemoveListener(event)
* Returns: [Element][Element]

Extendable **mousemove** listener

      mousemoveListener: -> this

---

### #mousedownListener(event)
* Returns: [Element][Element]

Extendable **mousemove** listener

      mousedownListener: -> this

---

### #mouseupListener(event)
* Returns: [Element][Element]

Extendable **mousemove** listener

      mouseupListener: -> this

[Element]: ./element

[Parent]: ./index.litcoffee
[Evented]: https://nhzio.github.com/ecl/evented.html
[Number]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number
[Object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object
[Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array

# CLASS: Element
* Extends: [Evented][Evented]
* Parent: [Index][Parent]

A visual element. Manages **mousemove**, **mousedown** and **mouseup** events.
Manages **regions** and **local event coordinates**.

---

    module.exports = class Element extends require 'ecl/dist/evented'

## CONSTRUCTOR

### new Element(args)

Creates an Element instance.

      constructor: ->
        super

Copy the element origin if it was set from named arguments

        if @origin then @origin = x:(@origin.x or 0), y:(@origin.y or 0)

Copy the element regions if they were set from named arguments

        if regions = @regions
          @regions = {}
          @regions[name] = region for name, region of regions

Register validating listeners for each **type** from the list.

        for type in ['mousemove', 'mousedown', 'mouseup']

Localize the **type** variable.

          do (type) =>

Add capturing listener which will cancel and stop the event
if the event type is not enabled.

            @addListener type:type, capture:yes, listener: (event) ->
              event.cancel().stop() unless @events?[type]

Add normal listener which will cancel and stop the event
if the event type is not enabled.

            @addListener type:type, capture:no, listener: (event) ->
              event.cancel().stop() unless @events?[type]

Register extendable listeners for each **type** from the list.

        for type in ['mousemove', 'mousedown', 'mouseup']

Add capturing extendable listener

          @addListener type, this["#{type}CaptureListener"], yes

Add normal extendable listener

          @addListener type, this["#{type}Listener"], no

#### PARAMETERS
* **args**
* Type: [Object][Object] - named arguments

* **args.origin**
* Type: [Object][Object] - origin coordinates

* **args.origin.x**
* Type: [Number][Number] - x coordinate

* **args.origin.y**
* Type: [Number][Number] - y coordinate

* **args.regions** - named regions
* Type: [Object][Object]

* **args.regions.{NAME}**
* Type: [Array][Array] - [x, y, width, height]

---

## PROPERTIES

### #origin
* Type: [Object][Object] - origin coordinates
    * **x**
      * Type: [Number][Number] - x coordinate
    * **y**
      * Type: [Number][Number] - y coordinate

### #regions
* Type: [Object][Object]
    * **{NAME}**
    * Type: [Array][Array] - [x, y, width, height]

---

## METHODS
### #localizeEventCoordinates(event) ->
* Returns: [Element][Element]

Helper method, to localize event coordinates.

      localizeEventCoordinates: (event) ->
        if event

If event already has localized **x**, use it instead of absolute **x**

          x = (if event.localX? then event.localX else event.x)
          event.localX = x - (@origin?.x or 0)

If event already has localized **y**, use it instead of absolute **y**

          y = (if event.localY? then event.localY else event.y)
          event.localY = y - (@origin?.y or 0)

        return this
---

### #getEventRegions(event)
* Returns: [Object][Object] - named set of matching regions

Helper method, to get element regions that match the event.

      getEventRegions: (event) ->
        result = null

Proceed only if event coordinates are localized

        if event and (x = event.localX)? and (y = event.localY)?

Walk through the element regions

          for name, $ of @regions

and add the region to the result if event localized coordinates are within
the region rectangle.

            if $[0] <= x <= ($[0] + $[2]) and $[1] <= y <= ($[1] + $[3])

Initialize result only if at least one region matches

              result or= {}
              result[name] = $

        return result

---

## LISTENERS
### #mousemoveCaptureListener(event)
* Returns: [Element][Element]

Extendable capturing **mousemove** listener

      mousemoveCaptureListener: (event) ->

Store the event in the runtime for later and localize event coordinates

        @___runtime.mousemoveEvent = event
        @localizeEventCoordinates event

        return this

---

### #mousedownCaptureListener(event)
* Returns: [Element][Element]

Extendable capturing **mousedown** listener

      mousedownCaptureListener: (event) ->

Store the event in the runtime for later and localize event coordinates

        @___runtime.mousedownEvent = event
        @localizeEventCoordinates event

        return this

---

### #mouseupCaptureListener(event)
* Returns: [Element][Element]

Extendable capturing **mouseup** listener

      mouseupCaptureListener: (event) ->

Store the event in the runtime for later and localize event coordinates

        @___runtime.mouseupEvent = event
        @localizeEventCoordinates event

        return this

---

### #mousemoveListener(event)
* Returns: [Element][Element]

Extendable **mousemove** listener

      mousemoveListener: -> this

---

### #mousedownListener(event)
* Returns: [Element][Element]

Extendable **mousemove** listener

      mousedownListener: -> this

---

### #mouseupListener(event)
* Returns: [Element][Element]

Extendable **mousemove** listener

      mouseupListener: -> this

[Element]: ./element

[Parent]: ./index.litcoffee
[Evented]: https://nhzio.github.com/ecl/evented.html
[Number]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number
[Object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object
[Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array


Optimize code

Optimize code

Frontend JavaScript for nodePush
================================
Copyright Information
---------------------

	"@author	Tim Düsterhus"
	"@copyright	2012-2013 Tim Düsterhus"
	"@license	BSD 3-Clause License <http://opensource.org/licenses/BSD-3-Clause>"
	"@package	be.bastelstu.wcf.nodePush"

Setup
-----
Ensure sane values for `$` and `window`

	(($, window) ->
		# Enable strict mode
		"use strict";

		# Ensure our namespace is present
		window.be ?= {}
		be.bastelstu ?= {}
		be.bastelstu.wcf ?= {}

Overwrite `console` to add the origin in front of the message

		console =
			log: (message) ->
				window.console.log "[be.bastelstu.wcf.nodePush] #{message}"
			warn: (message) ->
				window.console.warn "[be.bastelstu.wcf.nodePush] #{message}"
			error: (message) ->
				window.console.error "[be.bastelstu.wcf.nodePush] #{message}"

be.bastelstu.Chat
=================

Private Attributes
------------------

		socket = null
		connected = false
		initialized = false
		events =
			connect: $.Callbacks()
			disconnect: $.Callbacks()
			message: { }

		be.bastelstu.wcf.nodePush =

Methods
-------

**init(host)**
Initialize socket.io to enable nodePush.

			init: (host) ->
				return if initialized
				initialized = true
				console.log 'Initializing nodePush'

				unless window.io?
					console.error 'nodePush not available, aborting'
					return

				socket = window.io.connect host

				socket.on 'connect', ->
					connected = true
					console.log 'Connected to nodePush'
					events.connect.fire()

				socket.on 'disconnect', ->
					connected = false
					console.warn 'Lost connection to nodePush'
					events.disconnect.fire()

				socket.on 'message', (message) ->
					return unless events.message[message]?

					events.message[message].fire()

**onConnect(callback)**
Adds a new `callback` that will be called when a connection to nodePush is established. The given
`callback` will be called once if a connection is established at time of calling. Returns `true`
on success and `false` otherwise.

			onConnect: (callback) ->
				return false unless $.isFunction callback

				events.connect.add callback

				callback() if connected
				true

**onDisconnect(callback)**
Adds a new `callback` that will be called when the connection to nodePush is lost. Returns `true`
on success and `false` otherwise.

			onDisconnect: (callback) ->
				return false unless $.isFunction callback

				events.disconnect.add callback
				true

**onMessage(message, callback)**
Adds a new `callback` that will be called when the specified `message` is received. Returns `true`
on success and `false` otherwise.

			onMessage: (message, callback) ->
				false unless $.isFunction callback
				false unless /[a-zA-Z0-9-_.]+$/.test message

				events.message[message] ?= $.Callbacks()
				events.message[message].add callback
				true

	)(jQuery, @)

Frontend JavaScript for nodePush
================================
Copyright Information
---------------------

	"@author	Tim Düsterhus"
	"@copyright	2012-2013 Tim Düsterhus"
	"@license	BSD 3-Clause License <http://opensource.org/licenses/BSD-3-Clause>"
	"@package	be.bastelstu.wcf.nodePush"

Setup
-----
Ensure sane values for `$` and `window`

	(($, window) ->
		# Enable strict mode
		"use strict";

		# Ensure our namespace is present
		window.be ?= {}
		be.bastelstu ?= {}
		be.bastelstu.wcf ?= {}

Overwrite `console` to add the origin in front of the message

		console =
			log: (message) ->
				window.console.log "[be.bastelstu.wcf.nodePush] #{message}"
			warn: (message) ->
				window.console.warn "[be.bastelstu.wcf.nodePush] #{message}"
			error: (message) ->
				window.console.error "[be.bastelstu.wcf.nodePush] #{message}"

be.bastelstu.Chat
=================

Private Attributes
------------------

		socket = null
		connected = false
		initialized = false
		events =
			connect: $.Callbacks()
			disconnect: $.Callbacks()
			message: { }

		be.bastelstu.wcf.nodePush =

Methods
-------

**init(host)**
Initialize socket.io to enable nodePush.

			init: (host) ->
				return if initialized
				initialized = true
				console.log 'Initializing nodePush'

				unless window.io?
					console.error 'nodePush not available, aborting'
					return

				socket = window.io.connect host

				socket.on 'connect', =>
					connected = true
					console.log 'Connected to nodePush'
					events.connect.fire()

				socket.on 'disconnect', =>
					connected = false
					console.warn 'Lost connection to nodePush'
					events.disconnect.fire()

				socket.on 'message', (message) =>
					return unless events.message[message]?

					events.message[message].fire()

**onConnect(callback)**
Adds a new `callback` that will be called when a connection to nodePush is established. The given
`callback` will be called once if a connection is established at time of calling. Returns `true`
on success and `false` otherwise.

			onConnect: (callback) ->
				return false unless $.isFunction callback

				events.connect.add callback

				callback() if connected
				true

**onDisconnect(callback)**
Adds a new `callback` that will be called when the connection to nodePush is lost. Returns `true`
on success and `false` otherwise.

			onDisconnect: (callback) ->
				return false unless $.isFunction callback

				events.disconnect.add callback
				true

**onMessage(message, callback)**
Adds a new `callback` that will be called when the specified `message` is received. Returns `true`
on success and `false` otherwise.

			onMessage: (message, callback) ->
				false unless $.isFunction callback
				false unless /[a-zA-Z0-9-_.]+$/.test message

				events.message[message] ?= $.Callbacks()
				events.message[message].add callback
				true

	)(jQuery, @)


controller document 'false' and '@render' in beforeAction

# Batman.Controller

`Batman.Controller` is the base class from which all an application's controllers should descend from. `Batman.Controller`s are responsible for executing _actions_ which fire off requests for model data, render views, or redirect to other actions in response to navigation state changes.

## Controller Directory

`Batman.Controller`s are singletons which means each controller in your application is instantiated exactly once. The instance of each controller is available on the class at the `@sharedController` property, or within a `ControllerDirectory` on the `Application` class. See `Batman.Application.controllers`.

## Actions

Each `Batman.Controller` should have a number of instance level functions which can fetch the data needed and often render a view (or views) to display that data. These functions can be declared in typical CoffeeScript fashion like so:

```example
  class Alfred extends Batman.App
    @root "todos#index"

  class Alfred.TodosController extends Batman.Controller
    index: (params) ->
    show: (params) ->
```

Each action function receives the parameters from the dispatcher which are pulled out of the navigated-to URL. This includes both named route parameters (`/:foo` style) as well as arbitrary query parameters (`?foo=bar` style).

## routingKey and Minification

For functionality like contextual redirects and automatic view source and class inference, Batman needs to know the name of all your `Controller` subclasses. The usual way to do this is by using `Function::name` which Batman will use in development, but this is often mangled in minified production environments. For this reason, Batman will error out on boot if a `Controller` subclass' `routingKey` is not defined on the prototype. The `routingKey` is a `String` which remains unmangled after minification and thus allows Batman to continue using the aforementioned features. To disable this requirement (if you know your code won't ever be minified), set `Batman.config.minificationErrors` to false.

## ::render([options : [Object|boolean]])

`render` is used to control the rendering of the currently executed action. Passing `false` will prevent the current action from implicitly rendering the view. Passing an `options` object allows the default `view`, `source`, `viewClass` or `yield` block to be overridden, all of which are optional. If no parameters are passed to options, it will manually trigger the render function, rather than waiting for it to be implicitly called at the end of the action.

_Note_: For more information on yield blocks, see Controller::.defaultRenderYield

    class TestController extends Batman.Controller
      routingKey: 'test'

      nonVisibleAction: ->
        @render(false)

      customSource: ->
        @render(source: 'errors/404')

      customYield: ->
        @render(into: 'not-main')

      delayedRender: ->
        setTimeout (-> @render()), 1000
        @render(false)

## @beforeAction([options : [string|Object],] filter : [string|Function])

`@beforeAction` allows controllers to declare that a function should be executed before the body of an action during action execution. `@beforeAction` optionally accepts some options representing which action(s) to execute the filter before, and then a string naming a function or function proper to execute.

The `options` argument can take three forms to imply different things:

 1. `undefined`: implies that this filter function should be executed before all actions.
 2. a String: implies that this filter function should be executed before the action named by the string.
 3. an Object: implies that this filter function should be executed before the actions named by an Array at the `only` key in the options object, or before all actions excluding those named by an Array at the `except` key in the options object.

If any `beforeAction` filter returns `false` or calls [`@redirect`](/docs/api/batman.controller.html#prototype_function_redirect), the controller action won't be executed.

    test "@beforeAction allows declaration of filters to execute before an action", ->
      results = []

      class TestController extends Batman.Controller
        routingKey: "test"
        @beforeAction only: "index", -> results.push "before!"
        index: ->
          results.push "action!"
          @render false

      controller = TestController.get("sharedController")
      controller.dispatch "index"
      equal results[0], "before!"
      equal results[1], "action!"

    test "@beforeAction allows declaration of named filters to execute before an action", ->
      class TodoController extends Batman.Controller
        routingKey: "test"
        @beforeAction only: "show", "fetchTodo"
        fetchTodo: -> @set("todo", {isDone: true})
        show: ->
          @render false

      controller = TodoController.get("sharedController")
      controller.dispatch "show"
      deepEqual controller.get("todo"), {isDone: true}

    test "@beforeAction allows whitelisting or blacklisting filters to execute before an action", ->
      class TodoController extends Batman.Controller
        routingKey: "test"
        @beforeAction only: ["show", "edit"], "fetchTodo"
        @beforeAction except: ["index"], "prepareNewTodo"
        fetchTodo: -> @set "todo", {isDone: true}
        prepareNewTodo: -> @set "newTodo", {isDone: false}
        index: -> @render false
        show: -> @render false

      controller = TodoController.get("sharedController")
      controller.dispatch "show"
      deepEqual controller.get("todo"), {isDone: true}
      deepEqual controller.get("newTodo"), {isDone: false}

    test "@beforeAction allows declaration of filters to execute before all actions", ->
      results = []

      class TestController extends Batman.Controller
        routingKey: "test"
        @beforeAction -> results.push "before!"
        index: ->
          results.push "action!"
          @render false

      controller = TestController.get("sharedController")
      controller.dispatch "index"
      equal results[0], "before!"
      equal results[1], "action!"

## @afterAction([options : [string|Object],] filter : [string|Function])

`@afterAction` allows controllers to declare that a function should be run after the action body and all operations have completed during action execution. Functions declared by `@afterAction` will thus be run after the code of the action body and also after any redirects or renders have taken place and completed. `@afterAction` optionally accepts some options representing which action(s) to execute the filter after, and then a string naming a function or function proper to execute. See `Batman.Controller.beforeAction` for documentation on the structure of the options.

    test "@afterAction allows declaration of filters to execute after an action", ->
      results = []

      class TestController extends Batman.Controller
        routingKey: "test"
        @afterAction only: "index", -> results.push "after!"
        index: ->
          results.push "action!"
          @render false

      controller = TestController.get("sharedController")
      controller.dispatch "index"
      equal results[0], "action!"
      equal results[1], "after!"

    test "@afterAction allows declaration of named filters to execute after an action", ->
      result = null

      class TodoController extends Batman.Controller
        routingKey: "test"
        @afterAction only: "create", "notify"
        notify: -> result = "Todo created successfully."
        create: -> @render false

      controller = TodoController.get("sharedController")
      controller.dispatch "create"
      equal result, "Todo created successfully."

    test "@afterAction allows whitelisting or blacklisting filters to execute after an action", ->
      result = null

      class TodoController extends Batman.Controller
        routingKey: "test"
        @afterAction only: ["create", "update"], "notify"
        index: -> @render false
        create: -> @render false
        notify: -> result = "Todo created successfully."

      controller = TodoController.get("sharedController")
      controller.dispatch "index"
      equal result, null
      controller.dispatch "create"
      equal result, "Todo created successfully."

    test "@afterAction allows declaration of filters to execute after all actions", ->
      results = []

      class TestController extends Batman.Controller
        routingKey: "test"
        @afterAction -> results.push "after!"
        index: ->
          results.push "action!"
          @render false

      controller = TestController.get("sharedController")
      controller.dispatch "index"
      equal results[0], "action!"
      equal results[1], "after!"

## ::executeAction(action: string[, params: Object])

`executeAction` will run the `action` specified, running all `@beforeAction` and `@afterAction` filters that match that endpoint. Optionally, `params` can be passed into the new action for processing. If no params are passed, it will default to the params of the current action.

    test "executeAction will run the called action", ->
      result = null

      class TestController extends Batman.Controller
        routingKey: "test"
        show: (params) ->
          result = params.id
          @render false
        other: ->
          @executeAction("show", {id: 4})

      controller = TestController.get("sharedController")
      controller.dispatch "other"
      equal result, 4

    test "executeAction will run all @beforeAction and @afterAction filters", ->
      results = []

      class TestController extends Batman.Controller
        routingKey: "test"
        @beforeAction only: "index", -> results.push "before!"
        @afterAction only: "index", -> results.push "after!"
        index: ->
          results.push "action!"
          @render false
        other: ->
          @executeAction("index")

      controller = TestController.get("sharedController")
      controller.dispatch "other"
      deepEqual results, ["before!", "action!", "after!"]

## ::redirect(url: string)

`redirect` will forward the user to a new route defined by `url` without hard-refreshing the page. This modifies the current page URL using pushState if supported, otherwise falling back to hash-bang fragment identifiers. The url can be obtained dynamically using `MyApp.get('routes').myResourceName().path()` or by manually passing a string, such as `@redirect('/test')`.

## ::scrollToHash([hash: string])

`scrollToHash` emulates the native scrolling behaviour of the browser by allowing the URL to link to a specific ID on the page. The code will look for an element with a matching ID to `hash` if it is provided otherwise using the `#` parameter in the URL. If an element is found, the page will scroll to the matching element.

## ::.autoScrollToHash[= true] : boolean

`autoScrollToHash` is a boolean property on the instance level of a controller describing whether or not the controller should automatically scroll the browser window to the element with ID equal to the `hash` parameter. This behaviour emulates the native behaviour of the same nature, but is implemented in Batman so the functionality works after each dispatch (instead of each page refresh) and when Batman is using hash bang routing. `autoScrollToHash` is true by default.

## ::.defaultRenderYield[= 'main'] : string

`defaultRenderYield` is a `string` representing which yield a controller should automatically render into if no yield is mentioned explicitly. `defaultRenderYield` is `'main'` normally, which means calls to `@render()` or actions which rely on the implicit rendering render their views into the `main` yield (and end up wherever `data-yield="main"` is found in your HTML).

# Batman.Controller

`Batman.Controller` is the base class from which all an application's controllers should descend from. `Batman.Controller`s are responsible for executing _actions_ which fire off requests for model data, render views, or redirect to other actions in response to navigation state changes.

## Controller Directory

`Batman.Controller`s are singletons which means each controller in your application is instantiated exactly once. The instance of each controller is available on the class at the `@sharedController` property, or within a `ControllerDirectory` on the `Application` class. See `Batman.Application.controllers`.

## Actions

Each `Batman.Controller` should have a number of instance level functions which can fetch the data needed and often render a view (or views) to display that data. These functions can be declared in typical CoffeeScript fashion like so:

```example
  class Alfred extends Batman.App
    @root "todos#index"

  class Alfred.TodosController extends Batman.Controller
    index: (params) ->
    show: (params) ->
```

Each action function receives the parameters from the dispatcher which are pulled out of the navigated-to URL. This includes both named route parameters (`/:foo` style) as well as arbitrary query parameters (`?foo=bar` style).

## routingKey and Minification

For functionality like contextual redirects and automatic view source and class inference, Batman needs to know the name of all your `Controller` subclasses. The usual way to do this is by using `Function::name` which Batman will use in development, but this is often mangled in minified production environments. For this reason, Batman will error out on boot if a `Controller` subclass' `routingKey` is not defined on the prototype. The `routingKey` is a `String` which remains unmangled after minification and thus allows Batman to continue using the aforementioned features. To disable this requirement (if you know your code won't ever be minified), set `Batman.config.minificationErrors` to false.

## ::render([options : [Object|boolean]])

`render` is used to control the rendering of the currently executed action. Passing `false` will prevent the current action from implicitly rendering the view. Passing an `options` object allows the default `view`, `source`, `viewClass` or `yield` block to be overridden, all of which are optional. If no parameters are passed to options, it will manually trigger the render function, rather than waiting for it to be implicitly called at the end of the action.

_Note_: For more information on yield blocks, see Controller::.defaultRenderYield

    class TestController extends Batman.Controller
      routingKey: 'test'

      nonVisibleAction: ->
        @render(false)

      customSource: ->
        @render(source: 'errors/404')

      customYield: ->
        @render(into: 'not-main')

      delayedRender: ->
        setTimeout (-> @render()), 1000
        @render(false)

## @beforeAction([options : [string|Object],] filter : [string|Function])

`@beforeAction` allows controllers to declare that a function should be executed before the body of an action during action execution. `@beforeAction` optionally accepts some options representing which action(s) to execute the filter before, and then a string naming a function or function proper to execute.

The `options` argument can take three forms to imply different things:

 1. `undefined`: implies that this filter function should be executed before all actions.
 2. a String: implies that this filter function should be executed before the action named by the string.
 3. an Object: implies that this filter function should be executed before the actions named by an Array at the `only` key in the options object, or before all actions excluding those named by an Array at the `except` key in the options object.

    test "@beforeAction allows declaration of filters to execute before an action", ->
      results = []

      class TestController extends Batman.Controller
        routingKey: "test"
        @beforeAction only: "index", -> results.push "before!"
        index: ->
          results.push "action!"
          @render false

      controller = TestController.get("sharedController")
      controller.dispatch "index"
      equal results[0], "before!"
      equal results[1], "action!"

    test "@beforeAction allows declaration of named filters to execute before an action", ->
      class TodoController extends Batman.Controller
        routingKey: "test"
        @beforeAction only: "show", "fetchTodo"
        fetchTodo: -> @set("todo", {isDone: true})
        show: ->
          @render false

      controller = TodoController.get("sharedController")
      controller.dispatch "show"
      deepEqual controller.get("todo"), {isDone: true}

    test "@beforeAction allows whitelisting or blacklisting filters to execute before an action", ->
      class TodoController extends Batman.Controller
        routingKey: "test"
        @beforeAction only: ["show", "edit"], "fetchTodo"
        @beforeAction except: ["index"], "prepareNewTodo"
        fetchTodo: -> @set "todo", {isDone: true}
        prepareNewTodo: -> @set "newTodo", {isDone: false}
        index: -> @render false
        show: -> @render false

      controller = TodoController.get("sharedController")
      controller.dispatch "show"
      deepEqual controller.get("todo"), {isDone: true}
      deepEqual controller.get("newTodo"), {isDone: false}

    test "@beforeAction allows declaration of filters to execute before all actions", ->
      results = []

      class TestController extends Batman.Controller
        routingKey: "test"
        @beforeAction -> results.push "before!"
        index: ->
          results.push "action!"
          @render false

      controller = TestController.get("sharedController")
      controller.dispatch "index"
      equal results[0], "before!"
      equal results[1], "action!"

## @afterAction([options : [string|Object],] filter : [string|Function])

`@afterAction` allows controllers to declare that a function should be run after the action body and all operations have completed during action execution. Functions declared by `@afterAction` will thus be run after the code of the action body and also after any redirects or renders have taken place and completed. `@afterAction` optionally accepts some options representing which action(s) to execute the filter after, and then a string naming a function or function proper to execute. See `Batman.Controller.beforeAction` for documentation on the structure of the options.

    test "@afterAction allows declaration of filters to execute after an action", ->
      results = []

      class TestController extends Batman.Controller
        routingKey: "test"
        @afterAction only: "index", -> results.push "after!"
        index: ->
          results.push "action!"
          @render false

      controller = TestController.get("sharedController")
      controller.dispatch "index"
      equal results[0], "action!"
      equal results[1], "after!"

    test "@afterAction allows declaration of named filters to execute after an action", ->
      result = null

      class TodoController extends Batman.Controller
        routingKey: "test"
        @afterAction only: "create", "notify"
        notify: -> result = "Todo created successfully."
        create: -> @render false

      controller = TodoController.get("sharedController")
      controller.dispatch "create"
      equal result, "Todo created successfully."

    test "@afterAction allows whitelisting or blacklisting filters to execute after an action", ->
      result = null

      class TodoController extends Batman.Controller
        routingKey: "test"
        @afterAction only: ["create", "update"], "notify"
        index: -> @render false
        create: -> @render false
        notify: -> result = "Todo created successfully."

      controller = TodoController.get("sharedController")
      controller.dispatch "index"
      equal result, null
      controller.dispatch "create"
      equal result, "Todo created successfully."

    test "@afterAction allows declaration of filters to execute after all actions", ->
      results = []

      class TestController extends Batman.Controller
        routingKey: "test"
        @afterAction -> results.push "after!"
        index: ->
          results.push "action!"
          @render false

      controller = TestController.get("sharedController")
      controller.dispatch "index"
      equal results[0], "action!"
      equal results[1], "after!"

## ::executeAction(action: string[, params: Object])

`executeAction` will run the `action` specified, running all `@beforeAction` and `@afterAction` filters that match that endpoint. Optionally, `params` can be passed into the new action for processing. If no params are passed, it will default to the params of the current action.

    test "executeAction will run the called action", ->
      result = null

      class TestController extends Batman.Controller
        routingKey: "test"
        show: (params) ->
          result = params.id
          @render false
        other: ->
          @executeAction("show", {id: 4})

      controller = TestController.get("sharedController")
      controller.dispatch "other"
      equal result, 4

    test "executeAction will run all @beforeAction and @afterAction filters", ->
      results = []

      class TestController extends Batman.Controller
        routingKey: "test"
        @beforeAction only: "index", -> results.push "before!"
        @afterAction only: "index", -> results.push "after!"
        index: ->
          results.push "action!"
          @render false
        other: ->
          @executeAction("index")

      controller = TestController.get("sharedController")
      controller.dispatch "other"
      deepEqual results, ["before!", "action!", "after!"]

## ::redirect(url: string)

`redirect` will forward the user to a new route defined by `url` without hard-refreshing the page. This modifies the current page URL using pushState if supported, otherwise falling back to hash-bang fragment identifiers. The url can be obtained dynamically using `MyApp.get('routes').myResourceName().path()` or by manually passing a string, such as `@redirect('/test')`.

## ::scrollToHash([hash: string])

`scrollToHash` emulates the native scrolling behaviour of the browser by allowing the URL to link to a specific ID on the page. The code will look for an element with a matching ID to `hash` if it is provided otherwise using the `#` parameter in the URL. If an element is found, the page will scroll to the matching element.

## ::.autoScrollToHash[= true] : boolean

`autoScrollToHash` is a boolean property on the instance level of a controller describing whether or not the controller should automatically scroll the browser window to the element with ID equal to the `hash` parameter. This behaviour emulates the native behaviour of the same nature, but is implemented in Batman so the functionality works after each dispatch (instead of each page refresh) and when Batman is using hash bang routing. `autoScrollToHash` is true by default.

## ::.defaultRenderYield[= 'main'] : string

`defaultRenderYield` is a `string` representing which yield a controller should automatically render into if no yield is mentioned explicitly. `defaultRenderYield` is `'main'` normally, which means calls to `@render()` or actions which rely on the implicit rendering render their views into the `main` yield (and end up wherever `data-yield="main"` is found in your HTML).


classList for svg

classList for svg

List of SVG elements

    Tags =
     svg: 'a altGlyph altGlyphDef altGlyphItem animate animateColor
      animateMotion animateTransform circle clipPath color-profile cursor
      defs desc ellipse feBlend feColorMatrix feComponentTransfer
      feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap
      feDistantLight feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur
      feImage feMerge feMergeNode feMorphology feOffset fePointLight
      feSpecularLighting feSpotLight feTile feTurbulence
      filter font font-face font-face-format font-face-name font-face-src
      font-face-uri foreignObject g glyph glyphRef hkern image line
      linearGradient marker mask metadata missing-glyph mpath path pattern
      polygon polyline radialGradient rect script set stop style svg symbol
      text textPath title tref tspan use view vkern switch foreignObject'

List of HTML elements

     html: 'a abbr address article aside audio b bdi bdo blockquote body button
      canvas caption cite code colgroup datalist dd del details dfn div dl dt
      em fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 head header
      hgroup html i iframe ins kbd label legend li main map mark menu meter
      nav noscript object ol optgroup option output p pre progress q rp rt
      ruby s samp script section select small span strong style sub summary
      sup table tbody td textarea tfoot th thead time title tr u ul video'

     htmlVoid: 'area base br col command embed hr img input keygen link meta
      param source track wbr'

Wrapper for browser API

    Api =
     document: @document

#Weya DOM

    weyaDomCreate = ->

Weya object to be passed as `this`

     weya =
      _elem: null

Manipulating dom objects

     setStyles = (elem, styles) ->
      for k, v of styles
       if v?
        elem.style.setProperty k, v
       else
        elem.style.removeProperty k

     setEvents = (elem, events) ->
      for k, v of events
       elem.addEventListener k, v, false

     setAttributes = (elem, attrs) ->
      for k, v of attrs
       switch k
        when 'style' then setStyles elem, v
        when 'on' then setEvents elem, v
        else
         if v?
          elem.setAttribute k, v
         else
          elem.removeAttribute k

Parse id and class string

     parseIdClass = (str) ->
      res =
       id: null
       class: []

      for c, i in str.split "."
       if c.indexOf("#") is 0
        res.id = c.substr 1
       else if c isnt ""
        if not res.class?
         res.class = [c]
        else
         res.class.push c

      return res

Append a child element

     append = (ns, name, args) ->
      idClass = null
      contentText = null
      attrs = null
      contentFunction = null

      for arg, i in args
       switch typeof arg
        when 'function' then contentFunction = arg
        when 'object' then attrs = arg
        when 'string'
         if args.length is 1
          contentText = arg
         else
          c = arg.charAt 0

          if i is 0 and (c is '#' or c is '.')
           idClass = arg
          else
           contentText = arg

Keep a reference to parent element

      pElem = @_elem

Keep a reference of `elem` to return at the end of the function

      if ns?
       elem = @_elem = Api.document.createElementNS ns, name
      else
       elem = @_elem = Api.document.createElement name

      if idClass?
       idClass = parseIdClass idClass
       if idClass.id?
        elem.id = idClass.id
       if idClass.class?
        if elem.classList?
         for c in idClass.class
          elem.classList.add c
        else #For older browsers; does not work with svgs
         className = ''
         for c in idClass.class
          className += ' ' if className isnt ''
          className += "#{c}"
         elem.className = className

      if attrs?
       setAttributes elem, attrs

      if pElem?
       pElem.appendChild elem

      if contentFunction?
       contentFunction.call @
      else if contentText?
       elem.textContent = contentText

      @_elem = pElem
      return elem

Wrap `append`

     wrapAppend = (ns, name) ->
      ->
       append.call @, ns, name, arguments

Initialize

     for name in Tags.svg.split ' '
      weya[name] = wrapAppend "http://www.w3.org/2000/svg", name

     for name in Tags.html.split ' '
      weya[name] = wrapAppend "http://www.w3.org/1999/xhtml", name

     for name in Tags.htmlVoid.split ' '
      weya[name] = wrapAppend null, name

     return weya

#Weya Markup

    weyaMarkupCreate = ->

Weya object to be passed as `this`

     weya =
      _buf: null
      _indent: 0

Render components

     setStyles = (buf, styles) ->
      buf.push " style=\""
      for k, v of styles
       buf.push "#{k}:#{v};"
      buf.push "\""

     setEvents = (buf, events) ->
      for k, v of events
       buf.push " on#{k}=\"#{v}\""

     setAttributes = (buf, attrs) ->
      for k, v of attrs
       switch k
        when 'style' then setStyles buf, v
        when 'on' then setEvents buf, v
        else
         buf.push " #{k}=\"#{v}\""

     setIndent = (buf, indent) ->
      for i in [0...indent]
       buf.push " "

Parse id and class string

     parseIdClass = (str) ->
      res =
       id: null
       class: null

      for c, i in str.split "."
       if c.indexOf("#") is 0
        res.id = c.substr 1
       else if c isnt ""
        if not res.class?
         res.class = c
        else
         res.class += " #{c}"

      return res

Append a child element

     append = (ns, name, args) ->
      idClass = null
      contentText = null
      attrs = null
      contentFunction = null

      for arg, i in args
       switch typeof arg
        when 'function' then contentFunction = arg
        when 'object' then attrs = arg
        when 'string'
         if args.length is 1
          contentText = arg
         else
          c = arg.charAt 0

          if i is 0 and (c is '#' or c is '.')
           idClass = arg
          else
           contentText = arg

      buf = @_buf

      setIndent buf, @_indent

      buf.push "<#{name}"

      if idClass?
       idClass = parseIdClass idClass
       if idClass.id?
        buf.push " id=\"#{idClass.id}\""
       if idClass.class?
        buf.push " class=\"#{idClass.class}\""

      if attrs?
       setAttributes buf, attrs

Can close void elements (element that self close) with a `/>`

      buf.push ">\n"
      @_indent++

      if contentFunction?
       contentFunction.call @
      else if contentText?
       setIndent buf, @_indent
       buf.push contentText
       buf.push "\n"

      @_indent--
      setIndent buf, @_indent
      buf.push "</#{name}>\n"

Wrap `append`

     wrapAppend = (ns, name) ->
      ->
       append.call @, ns, name, arguments

Initialize

     for name in Tags.svg.split ' '
      weya[name] = wrapAppend "http://www.w3.org/2000/svg", name

     for name in Tags.html.split ' '
      weya[name] = wrapAppend null, name

     for name in Tags.htmlVoid.split ' '
      weya[name] = wrapAppend null, name

     return weya

#Weya API

    weyaDom = weyaDomCreate()
    weyaMarkup = weyaMarkupCreate()

Create and append to `options.elem`. If `options.context` is provied it can be
accessed via `@$`. If `options.elem` is `null`, the element is created but
not appended.

    @Weya = Weya = (options, func) ->
     weya = weyaDom
     pContext = weya.$
     weya.$ = options.context
     pElem = weya._elem
     weya._elem = options.elem
     r = func?.call weya
     weya._elem = pElem
     weya.$ = pContext
     return r

    Weya.markup = (options, func) ->
     weya = weyaMarkup
     pContext = weya.$
     weya.$ = options.context
     pBuf = weya._buf
     weya._buf = []
     r = func?.call weya
     buf = weya._buf
     weya._buf = pBuf
     weya.$ = pContext
     return buf.join ''

    Weya.setApi = (api) ->
     for k, v of api
      Api[k] = v

    if module?
     module.exports = Weya

List of SVG elements

    Tags =
     svg: 'a altGlyph altGlyphDef altGlyphItem animate animateColor
      animateMotion animateTransform circle clipPath color-profile cursor
      defs desc ellipse feBlend feColorMatrix feComponentTransfer
      feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap
      feDistantLight feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur
      feImage feMerge feMergeNode feMorphology feOffset fePointLight
      feSpecularLighting feSpotLight feTile feTurbulence
      filter font font-face font-face-format font-face-name font-face-src
      font-face-uri foreignObject g glyph glyphRef hkern image line
      linearGradient marker mask metadata missing-glyph mpath path pattern
      polygon polyline radialGradient rect script set stop style svg symbol
      text textPath title tref tspan use view vkern switch foreignObject'

List of HTML elements

     html: 'a abbr address article aside audio b bdi bdo blockquote body button
      canvas caption cite code colgroup datalist dd del details dfn div dl dt
      em fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 head header
      hgroup html i iframe ins kbd label legend li main map mark menu meter
      nav noscript object ol optgroup option output p pre progress q rp rt
      ruby s samp script section select small span strong style sub summary
      sup table tbody td textarea tfoot th thead time title tr u ul video'

     htmlVoid: 'area base br col command embed hr img input keygen link meta
      param source track wbr'

Wrapper for browser API

    Api =
     document: @document

#Weya DOM

    weyaDomCreate = ->

Weya object to be passed as `this`

     weya =
      _elem: null

Manipulating dom objects

     setStyles = (elem, styles) ->
      for k, v of styles
       if v?
        elem.style.setProperty k, v
       else
        elem.style.removeProperty k

     setEvents = (elem, events) ->
      for k, v of events
       elem.addEventListener k, v, false

     setAttributes = (elem, attrs) ->
      for k, v of attrs
       switch k
        when 'style' then setStyles elem, v
        when 'on' then setEvents elem, v
        else
         if v?
          elem.setAttribute k, v
         else
          elem.removeAttribute k

Parse id and class string

     parseIdClass = (str) ->
      res =
       id: null
       class: []

      for c, i in str.split "."
       if c.indexOf("#") is 0
        res.id = c.substr 1
       else if c isnt ""
        if not res.class?
         res.class = [c]
        else
         res.class.push c

      return res

Append a child element

     append = (ns, name, args) ->
      idClass = null
      contentText = null
      attrs = null
      contentFunction = null

      for arg, i in args
       switch typeof arg
        when 'function' then contentFunction = arg
        when 'object' then attrs = arg
        when 'string'
         if args.length is 1
          contentText = arg
         else
          c = arg.charAt 0

          if i is 0 and (c is '#' or c is '.')
           idClass = arg
          else
           contentText = arg

Keep a reference to parent element

      pElem = @_elem

Keep a reference of `elem` to return at the end of the function

      if ns?
       elem = @_elem = Api.document.createElementNS ns, name
      else
       elem = @_elem = Api.document.createElement name

      if idClass?
       idClass = parseIdClass idClass
       if idClass.id?
        elem.id = idClass.id
       if idClass.class?
        className = ''
        for c in idClass.class
         className += ' ' if className isnt ''
         className += "#{c}"
        elem.className = className

      if attrs?
       setAttributes elem, attrs

      if pElem?
       pElem.appendChild elem

      if contentFunction?
       contentFunction.call @
      else if contentText?
       elem.textContent = contentText

      @_elem = pElem
      return elem

Wrap `append`

     wrapAppend = (ns, name) ->
      ->
       append.call @, ns, name, arguments

Initialize

     for name in Tags.svg.split ' '
      weya[name] = wrapAppend "http://www.w3.org/2000/svg", name

     for name in Tags.html.split ' '
      weya[name] = wrapAppend "http://www.w3.org/1999/xhtml", name

     for name in Tags.htmlVoid.split ' '
      weya[name] = wrapAppend null, name

     return weya

#Weya Markup

    weyaMarkupCreate = ->

Weya object to be passed as `this`

     weya =
      _buf: null
      _indent: 0

Render components

     setStyles = (buf, styles) ->
      buf.push " style=\""
      for k, v of styles
       buf.push "#{k}:#{v};"
      buf.push "\""

     setEvents = (buf, events) ->
      for k, v of events
       buf.push " on#{k}=\"#{v}\""

     setAttributes = (buf, attrs) ->
      for k, v of attrs
       switch k
        when 'style' then setStyles buf, v
        when 'on' then setEvents buf, v
        else
         buf.push " #{k}=\"#{v}\""

     setIndent = (buf, indent) ->
      for i in [0...indent]
       buf.push " "

Parse id and class string

     parseIdClass = (str) ->
      res =
       id: null
       class: null

      for c, i in str.split "."
       if c.indexOf("#") is 0
        res.id = c.substr 1
       else if c isnt ""
        if not res.class?
         res.class = c
        else
         res.class += " #{c}"

      return res

Append a child element

     append = (ns, name, args) ->
      idClass = null
      contentText = null
      attrs = null
      contentFunction = null

      for arg, i in args
       switch typeof arg
        when 'function' then contentFunction = arg
        when 'object' then attrs = arg
        when 'string'
         if args.length is 1
          contentText = arg
         else
          c = arg.charAt 0

          if i is 0 and (c is '#' or c is '.')
           idClass = arg
          else
           contentText = arg

      buf = @_buf

      setIndent buf, @_indent

      buf.push "<#{name}"

      if idClass?
       idClass = parseIdClass idClass
       if idClass.id?
        buf.push " id=\"#{idClass.id}\""
       if idClass.class?
        buf.push " class=\"#{idClass.class}\""

      if attrs?
       setAttributes buf, attrs

Can close void elements (element that self close) with a `/>`

      buf.push ">\n"
      @_indent++

      if contentFunction?
       contentFunction.call @
      else if contentText?
       setIndent buf, @_indent
       buf.push contentText
       buf.push "\n"

      @_indent--
      setIndent buf, @_indent
      buf.push "</#{name}>\n"

Wrap `append`

     wrapAppend = (ns, name) ->
      ->
       append.call @, ns, name, arguments

Initialize

     for name in Tags.svg.split ' '
      weya[name] = wrapAppend "http://www.w3.org/2000/svg", name

     for name in Tags.html.split ' '
      weya[name] = wrapAppend null, name

     for name in Tags.htmlVoid.split ' '
      weya[name] = wrapAppend null, name

     return weya

#Weya API

    weyaDom = weyaDomCreate()
    weyaMarkup = weyaMarkupCreate()

Create and append to `options.elem`. If `options.context` is provied it can be
accessed via `@$`. If `options.elem` is `null`, the element is created but
not appended.

    @Weya = Weya = (options, func) ->
     weya = weyaDom
     pContext = weya.$
     weya.$ = options.context
     pElem = weya._elem
     weya._elem = options.elem
     r = func?.call weya
     weya._elem = pElem
     weya.$ = pContext
     return r

    Weya.markup = (options, func) ->
     weya = weyaMarkup
     pContext = weya.$
     weya.$ = options.context
     pBuf = weya._buf
     weya._buf = []
     r = func?.call weya
     buf = weya._buf
     weya._buf = pBuf
     weya.$ = pContext
     return buf.join ''

    Weya.setApi = (api) ->
     for k, v of api
      Api[k] = v

    if module?
     module.exports = Weya


Live reload

Live reload

# Strapped

*Shrub's default skin.*

    path = require 'path'

    shrubSkin = require 'shrub-skin'

    exports.pkgmanRegister = (registrar) ->

#### Implements hook `shrubNodemailerHtml`.

      registrar.registerHook 'shrubNodemailerHtml', ($body, html, $) ->

        $('.container > .main', $body).html html

        $body.find('.navbar-toggle, .navbar-collapse').remove()
        $body.find('[data-shrub-ui-messages]').remove()

        $body.find('script').remove()

        $('noscript', $body).remove()

#### Implements hook `shrubSkinAssets`.

      registrar.registerHook 'shrubSkinAssets', (assets) ->

Add our future-compiled LESS style sheets.

        assets.styleSheets.default.push '/css/style.css'
        assets.styleSheets.production.push '/css/style.css'

#### Implements hook `shrubGruntConfig`.

      registrar.registerHook 'shrubGruntConfig', (gruntConfig) ->

        gruntConfig.less ?= {}

        shrubSkin.gruntSkin gruntConfig, 'shrub-skin-strapped'

        gruntConfig.configureTask 'less', 'shrub-skin-strapped', files: [
          src: [
            "#{__dirname}/app/less/style.less"
          ]
          dest: 'app/skin/shrub-skin-strapped/css/style.css'
        ]

        gruntConfig.configureTask(
          'watch', 'shrub-skin-strappedLess'

          files: [
            "#{__dirname}/app/less/style.less"
          ]
          tasks: [
            'newer:less:shrub-skin-strapped'
          ]
          options: livereload: true
        )

        gruntConfig.registerTask 'build:shrub-skin-strapped', [
          'clean:shrub-skin-strapped'
          'newer:copy:shrub-skin-strapped'
          'less:shrub-skin-strapped'
        ]

        gruntConfig.registerTask 'build', ['build:shrub-skin-strapped']

        gruntConfig.loadNpmTasks ['grunt-contrib-less']

# Strapped

*Shrub's default skin.*

    path = require 'path'

    shrubSkin = require 'shrub-skin'

    exports.pkgmanRegister = (registrar) ->

#### Implements hook `shrubNodemailerHtml`.

      registrar.registerHook 'shrubNodemailerHtml', ($body, html, $) ->

        $('.container > .main', $body).html html

        $body.find('.navbar-toggle, .navbar-collapse').remove()
        $body.find('[data-shrub-ui-messages]').remove()

        $body.find('script').remove()

        $('noscript', $body).remove()

#### Implements hook `shrubSkinAssets`.

      registrar.registerHook 'shrubSkinAssets', (assets) ->

Add our future-compiled LESS style sheets.

        assets.styleSheets.default.push '/css/style.css'
        assets.styleSheets.production.push '/css/style.css'

#### Implements hook `shrubGruntConfig`.

      registrar.registerHook 'shrubGruntConfig', (gruntConfig) ->

        gruntConfig.less ?= {}

        shrubSkin.gruntSkin gruntConfig, 'shrub-skin-strapped'

        gruntConfig.configureTask 'less', 'shrub-skin-strapped', files: [
          src: [
            "#{__dirname}/app/less/style.less"
          ]
          dest: 'app/skin/shrub-skin-strapped/css/style.css'
        ]

        gruntConfig.configureTask(
          'watch', 'shrub-skin-strappedLess'

          files: [
            "#{__dirname}/app/less/style.less"
          ]
          tasks: [
            'newer:less:shrub-skin-strapped'
          ]
        )

        gruntConfig.registerTask 'build:shrub-skin-strapped', [
          'clean:shrub-skin-strapped'
          'newer:copy:shrub-skin-strapped'
          'less:shrub-skin-strapped'
        ]

        gruntConfig.registerTask 'build', ['build:shrub-skin-strapped']

        gruntConfig.loadNpmTasks ['grunt-contrib-less']


Set up MongoDB query

Set up MongoDB query

    MongoQS     = require 'mongo-querystring'
    Document    = require './model/Document'
    stringify   = require('JSONStream').stringify

    sentry      = require './db/sentry'
    mongo       = require './db/mongo'

    collections = [
      'turer'
      'steder'
      'grupper'
      'områder'
      'bilder'
      'arrangementer'
    ]

    qs = new MongoQS
      alias:
        tag: 'tags.0'
        gruppe: 'grupper'
        endret: 'after'
      ignore:
        api_key     : true # other use
        order       : true # reserved
        sort        : true # other use
        limit       : true # other use
        skip        : true # other use
        fields      : true # other use
        _id         : true # use API endpoint
      custom:
        bbox: 'geojson'
        near: 'geojson'
        after: 'endret'

## PARAM {collection}

    exports.param = (req, res, next, col) ->
      if col not in ['turer', 'steder', 'grupper', 'områder', 'bilder', 'arrangementer']
        return res.json 404, message: 'Objekttype ikke funnet'

      req.type = col
      req.db   = col: mongo[col], query: {}

      next()

## OPTIONS /{collection}

    exports.options = (req, res, next) ->
      res.setHeader 'Access-Control-Expose-Headers', [
        'ETag', 'Location', 'Last-Modified', 'Count-Return', 'Count-Total'
      ].join(', ')
      res.setHeader 'Access-Control-Max-Age', 86400
      res.setHeader 'Access-Control-Allow-Headers', 'Content-Type'
      res.setHeader 'Access-Control-Allow-Methods', 'HEAD, GET, POST'
      res.send 204

## HEAD /{collection}
## GET /{collection}

    exports.get = (req, res, next) ->

### Query

      req.db.query = qs.parse req.query

Prevent private documents for other API user from being returned when quering
`tilbyder` and `status` fields.

      for key, val of req.query
        switch key
          when 'status'
            req.db.query.tilbyder = req.user if val not in ['Offentlig', 'Slettet']
            break
          when 'tilbyder'
            req.db.query.status = 'Offentlig' if val isnt req.user
            break
          else
            if key.substr(0,6) is 'privat'
              req.db.query.tilbyder = req.user
              break

Apply default access control unless `status` or `tilbyder` fields are already
queried.

      if not req.db.query.tilbyder or req.db.query.status
        req.db.query.$or = [{status: 'Offentlig'}, {tilbyder: req.usr}]

### Fields

      if typeof req.query.fields is 'string' and req.query.fields
        fields = {}
        for field in req.query.fields.split ',' when field.substr(0,6) isnt 'privat'
          fields[field] = true

      else
        fields =
          tilbyder: true
          endret: true
          status: true
          lisens: true
          navn: true
          tags: true

### Sort

Limit sort to ascending or descending on `endret` and `navn` since they are
indexed, non-indexed fields could be slower.

      if typeof req.query.sort is 'string' and req.query.sort
        sort = switch req.query.sort
          when 'endret' then [['endret', 1]]
          when '-endret' then [['endret', -1]]
          when 'navn' then [['navn', 1]]
          when '-navn' then [['navn', -1]]
          else 'endret'

Only apply default sort if there are no geospatial queries.

      else
        sort = 'endret' if not req.db.query.geojson

### Execute

      options =
        limit: Math.min((parseInt(req.query.limit, 10) or 20), 50)
        skip: parseInt(req.query.skip, 10) or 0
        sort: sort

Retrive matching documents from MongoDB.

      cursor = req.db.col.find(req.db.query, fields, options)
      cursor.count (err, total) ->
        return next err if err
        res.set 'Count-Return', Math.min(options.limit, total)
        res.set 'Count-Total', total
        return res.send 204 if req.method is 'HEAD'
        return res.json documents: [], count: 0, total: 0 if total is 0
        res.set 'Content-Type', 'application/json; charset=utf-8'

Calculate number of rows returned since we don't know that in advanced (due to
the nature of streaming).

        count = Math.min(options.limit, Math.max(total - options.skip, 0))

Stream documents user in order to prevent loading them into memory.

        op = '{"documents":['
        cl = '],"count":' + count + ',"total":' + total + '}'

        cursor.stream().pipe(stringify(op, ',', cl)).pipe(res)

## POST /{collection}

    exports.post = (req, res, next) ->
      return res.json 400, message: 'Body is missing' if Object.keys(req.body).length is 0
      return res.json 422, message: 'Body should be a JSON Hash' if req.body instanceof Array

      req.body.tilbyder = req.usr

      new Document(req.type, null).once('error', next).once 'ready', ->
        @insert req.body, (err, warn, data) ->
          if err
            return next(err) if err.name isnt 'ValidationError'

            sentry.captureDocumentError req, err

            return res.json 422,
              document: req.body
              message: 'Validation Failed'
              errors: err.details #TODO(starefossen) document this

          res.set 'ETag', "\"#{data.checksum}\""
          res.set 'Last-Modified', new Date(data.endret).toUTCString()
          # res.set 'Location', req.get 'host'

          return res.json 201,
            document: data
            message: 'Validation Warnings' if warn.length > 0
            warnings: warn if warn.length > 0

    MongoQS     = require 'mongo-querystring'
    Document    = require './model/Document'
    stringify   = require('JSONStream').stringify

    sentry      = require './db/sentry'
    mongo       = require './db/mongo'

    collections = [
      'turer'
      'steder'
      'grupper'
      'områder'
      'bilder'
      'arrangementer'
    ]

    qs = new MongoQS
      alias:
        tag: 'tags.0'
        gruppe: 'grupper'
        endret: 'after'
      ignore:
        api_key     : true # other use
        order       : true # reserved
        sort        : true # other use
        limit       : true # other use
        skip        : true # other use
        fields      : true # other use
        _id         : true # use API endpoint
      custom:
        bbox: 'geojson'
        near: 'geojson'
        after: 'endret'

## PARAM {collection}

    exports.param = (req, res, next, col) ->
      if col not in ['turer', 'steder', 'grupper', 'områder', 'bilder', 'arrangementer']
        return res.json 404, message: 'Objekttype ikke funnet'

      req.type = col
      req.db   = col: mongo[col], query: {}

      next()

## OPTIONS /{collection}

    exports.options = (req, res, next) ->
      res.setHeader 'Access-Control-Expose-Headers', [
        'ETag', 'Location', 'Last-Modified', 'Count-Return', 'Count-Total'
      ].join(', ')
      res.setHeader 'Access-Control-Max-Age', 86400
      res.setHeader 'Access-Control-Allow-Headers', 'Content-Type'
      res.setHeader 'Access-Control-Allow-Methods', 'HEAD, GET, POST'
      res.send 204

## HEAD /{collection}
## GET /{collection}

    exports.get = (req, res, next) ->

### Query

      req.db.query = qs.parse req.query

Prevent private documents for other API user from being returned when quering
`tilbyder` and `status` fields.

      for key, val of req.query
        switch key
          when 'status'
            req.db.query.tilbyder = req.user if val not in ['Offentlig', 'Slettet']
            break
          when 'tilbyder'
            req.db.query.status = 'Offentlig' if val isnt req.user
            break
          else
            if key.substr(0,6) is 'privat'
              req.db.query.tilbyder = req.user
              break

Apply default access control unless `status` or `tilbyder` fields are already
queried.

      if not req.db.query.tilbyder or req.db.query.status
        req.db.query.$or = [{status: 'Offentlig'}, {tilbyder: req.usr}]

### Fields

      if typeof req.query.fields is 'string' and req.query.fields
        fields = {}
        for field in req.query.fields.split ',' when field.substr(0,6) isnt 'privat'
          fields[field] = true

      else
        fields =
          tilbyder: true
          endret: true
          status: true
          lisens: true
          navn: true
          tags: true

### Sort

Limit sort to ascending or descending on `endret` and `navn` since they are
indexed, non-indexed fields could be slower.

      if typeof req.query.sort is 'string' and req.query.sort
        sort = switch req.query.sort
          when 'endret' then [['endret', 1]]
          when '-endret' then [['endret', -1]]
          when 'navn' then [['navn', 1]]
          when '-navn' then [['navn', -1]]
          else 'endret'

Only apply default sort if there are no geospatial queries.

      else
        sort = 'endret' if not req.db.query.geojson

Set up MongoDB options.

      options =
        limit: Math.min((parseInt(req.query.limit, 10) or 20), 50)
        skip: parseInt(req.query.skip, 10) or 0
        sort: 'endret'

Retrive matching documents from MongoDB.

      cursor = req.db.col.find(query, fields, options)
      cursor.count (err, total) ->
        return next err if err
        res.set 'Count-Return', Math.min(options.limit, total)
        res.set 'Count-Total', total
        return res.send 204 if req.method is 'HEAD'
        return res.json documents: [], count: 0, total: 0 if total is 0
        res.set 'Content-Type', 'application/json; charset=utf-8'

Calculate number of rows returned since we don't know that in advanced (due to
the nature of streaming).

        count = Math.min(options.limit, Math.max(total - options.skip, 0))

Stream documents user in order to prevent loading them into memory.

        op = '{"documents":['
        cl = '],"count":' + count + ',"total":' + total + '}'

        cursor.stream().pipe(stringify(op, ',', cl)).pipe(res)

## POST /{collection}

    exports.post = (req, res, next) ->
      return res.json 400, message: 'Body is missing' if Object.keys(req.body).length is 0
      return res.json 422, message: 'Body should be a JSON Hash' if req.body instanceof Array

      req.body.tilbyder = req.usr

      new Document(req.type, null).once('error', next).once 'ready', ->
        @insert req.body, (err, warn, data) ->
          if err
            return next(err) if err.name isnt 'ValidationError'

            sentry.captureDocumentError req, err

            return res.json 422,
              document: req.body
              message: 'Validation Failed'
              errors: err.details #TODO(starefossen) document this

          res.set 'ETag', "\"#{data.checksum}\""
          res.set 'Last-Modified', new Date(data.endret).toUTCString()
          # res.set 'Location', req.get 'host'

          return res.json 201,
            document: data
            message: 'Validation Warnings' if warn.length > 0
            warnings: warn if warn.length > 0