<commit_before>var assert = require('assert')

var RBTree = require('bintrees').RBTree

var cadence = require('cadence')

var Reactor = require('reactor')

var Id = require('paxos/id')
var Legislator = require('paxos/legislator')
var Client = require('paxos/client')

var Monotonic = require('monotonic')

var Scheduler = require('happenstance')

var interrupt = require('interrupt').createInterrupter()

function Kibitzer (id, options) {
    assert(id != null, 'id is required')
    id = (options.preferred ? 'a' : '7') + id

    options.ping || (options.ping = 250)
    options.timeout || (options.timeout = 1000)
    this.poll = options.poll || 5000
    this.ping = options.ping
    this.timeout = options.timeout

    this.syncLength = options.syncLength || 250

    this.preferred = !! options.preferred

    this._reactor = new Reactor({ object: this, method: '_tick' })

    this.happenstance = new Scheduler
    this._Date = options.Date || Date

    this.joining = []

    this.url = options.url
    this._ua = options.ua

    this.islandId = null
    this.baseId = id
    this.suffix = '0'

    this.legislator = this._createLegislator()
    this.client = new Client(this.legislator.id)

    this.cookies = {}
    this.logger = options.logger || function () {}

    this.discovery = options.discovery

    this.available = false

    this.waits = new RBTree(function (a, b) { return Id.compare(a.promise, b.promise) })
}

Kibitzer.prototype._createLegislator = function () {
    var suffix = this.suffix
    var words = Monotonic.parse(this.suffix)
    this.suffix = Monotonic.toString(Monotonic.increment(words))
    return new Legislator(this.baseId + suffix, {
        prefer: function (id) { return id[0] === 'a' },
        ping: this.ping,
        timeout: this.timeout
    })
}

Kibitzer.prototype._tick = cadence(function (async) {
    var dirty = false
    async(function () {
        var outgoing = this.client.outbox()
        if (outgoing.length) {
            var post
            async(function () {
                var url = this.legislator.location[this.legislator.government.majority[0]]
                this._ua.enqueue(url, post = {
                    entries: outgoing
                }, async())
            }, function (body) {
                var published = body ? body.entries : []
                this.logger('info', 'enqueued', {
                    kibitzerId: this.legislator.id,
                    sent: post,
                    received: body
                })
                this.client.published(published)
            })
            dirty = true
        }
    }, function () {
        async.forEach(function (route) {
            var forwards = this.legislator.forwards(route, 0), serialized
            async(function () {
                serialized = {
                    route: route,
                    index: 1,
                    messages: serializer.flatten(forwards)
                }
                var url = this.legislator.location[route.path[1]]
                this._ua.receive(url, serialized, async())
            }, function (body) {
                var returns = body ? body.returns : []
                this.logger('info', 'published', {
                    kibitzerId: this.legislator.id,
                    sent: serialized,
                    received: body
                })
                this.legislator.inbox(route, returns)
                this.legislator.sent(route, forwards, returns)
            })
            dirty = true
        })(this.legislator.outbox())
    }, function () {
        var entries = this.legislator.since(this.client.uniform)
        if (entries.length) {
            this.logger('info', 'consuming', {
                kibitzerId: this.legislator.id,
                entries: entries
            })
            var promise = this.client.receive(entries)
            async.forEach(function (entry) {
                this.logger('info', 'consume', {
                    kibitzerId: this.legislator.id,
                    entry: entry
                })
                var callback = this.cookies[entry.cookie]
                if (callback) {
                    callback(null, entry)
                    delete this.cookies[entry.cookie]
                }
                var wait
                while ((wait = this.waits.min()) && Id.compare(wait.promise, entry.promise) <= 0) {
                    wait.callbacks.forEach(function (callback) { callback() })
                    this.waits.remove(wait)
                }
            })(this.client.since(promise))
            dirty = true
        }
    }, function () {
        if (dirty) {
            this._reactor.check()
        }
    })
})

Kibitzer.prototype._checkSchedule2 = cadence(function (async) {
    async.forEach(function (event) {
        var method = 'when' + event[0].toUpperCase() + event.substring(1)
        this[method](event, async())
    })(this.happenstance.check())
})

Kibitzer.prototype._schedule = function (type, delay) {
    this.happenstance.schedule(this.legislator.id, type, this._Date.now() + delay)
}

Kibitzer.prototype._checkSchedule = function () {
    this._interval = setInterval(function () {
        if (this.legislator.checkSchedule()) {
            this.publisher.nudge()
        }
        this.scheduler.nudge()
    }.bind(this), 50)
}

Kibitzer.prototype._urls = function () {
    var urls = []
    for (var key in this.legislator.location) {
        urls.push(this.legislator.location[key])
    }
    return urls
}

Kibitzer.prototype.discover = cadence(function (async) {
    var urls = this._urls()
    if (urls.length) {
        return { id: this.legislator.id, urls: urls }
    } else {
        return null
    }
})

Kibitzer.prototype.pull = cadence(function (async, url) {
    assert(url, 'url is missing')
    var dataset = 'log', post, next = null
    var sync = async(function () {
        this._ua.sync(url, post = {
            kibitzerId: this.legislator.id,
            dataset: dataset,
            next: next
        }, async())
    }, function (body) {
        this.logger('info', 'pulled', {
            kibitzerId: this.legislator.id,
            url: url,
            sent: post,
            received: body
        })
        if (!body) {
            throw this._unexceptional(new Error('unable to sync'))
        } else {
            this._schedule('joining', this.timeout)
            switch (dataset) {
            case 'log':
                this.legislator.inject(body.entries)
                if (body.next == null) {
                    // todo: fast forward client!
                    dataset = 'meta'
                    next = null
                }
                next = body.next
                break
            case 'meta':
                this.legislator.location = body.location
                this.since = body.promise
                return [ sync.break ]
            }
        }
    })()
})

Kibitzer.prototype._sync = cadence(function (async, post) {
    if (!this.available) {
        this.logger('info', 'sync', {
            kibitzerId: this.legislator.id,
            available: this.available,
            received: request.body
        })
        interrupt.panic(new Error, 'unsyncable')
    }
    var response
    switch (post.dataset) {
    case 'log':
         response = this.legislator.extract('reverse', 24, post.next)
         break
    case 'meta':
        response = {
            // todo: oh, so this is public
            promise: this.legislator._greatestOf(this.legislator.id).uniform,
            location: this.legislator.location
        }
        break
    }
    this.logger('info', 'sync', {
        kibitzerId: this.legislator.id,
        available: this.available,
        post: post,
        response: response
    })
    return response
})

Kibitzer.prototype.join = cadence(function (async, url) {
    if (this._urls().length) {
        return []
    }
    this.joining.push(async())
    this._schedule('join', 0)
})

Kibitzer.prototype._joined = function () {
    this.joining.splice(0, this.joining.length).forEach(function (callback) { callback() })
}

Kibitzer.prototype._naturalize = cadence(function (async, body) {
    this.logger('info', 'naturalize', {
        kibitzerId: this.legislator.id,
        preferred: this.preferred,
        received: body
    })
    async(function () {
        this.pull(body.urls[0], async())
    }, function () {
        this.bootstrapped = false
        this.legislator.immigrate(this.legislator.id)
        this.legislator.initialize()
        this.client.prime(this.legislator.prime(this.since))
        assert(this.client.length, 'no entries in client')
        this._reactor.turnstile.workers = 1
        this._reactor.check()
        this._schedule('joining', this.timeout)
        this.available = true
        this.publish({
            type: 'naturalize',
            id: this.legislator.id,
            location: this.url
        }, true, async())
    }, function () {
        this._joined()
    })
})

Kibitzer.prototype._rejoin = cadence(function (async, body) {
    async(function () {
        this.available = false
        this.client.clear().forEach(function (request) {
            var callback = this.cookies[request.cookie]
            assert(callback, 'request missing callback')
            delete this.cookies[request.cookie]
            callback(this._unexceptional(new Error('rejoining')))
        }, this)
        this.scram(async())
    }, function () {
        if (body) this._naturalize(body, async())
        else this._schedule('join', 0)
    })
})

Kibitzer.prototype.bootstrap = function (async) {
    this.bootstrapped = true
    this._reactor.turnstile.workers = 1
    this.legislator.bootstrap()
    this.logger('info', 'bootstrap', {
        kibitzerId: this.legislator.id,
        preferred: this.preferred
    })
    this.client.prime(this.legislator.prime('1/0'))
    this.legislator.location[this.legislator.id] = this.url
    this.available = true
}

Kibitzer.prototype.whenJoin = cadence(function (async) {
    async(function () {
        this._ua.discover(async())
    }, function (body, okay) {
        this.logger('info', 'join', {
            okay: okay,
            kibitzerId: this.legislator.id,
            received: JSON.stringify(body),
            preferred: this.preferred
        })
        if (okay) {
            this._naturalize(body, async())
        } else {
            this._schedule('join', this.timeout)
        }
    })
})

Kibitzer.prototype.whenJoining = cadence(function (async) {
    this.logger('info', 'joining', {
        kibitzerId: this.legislator.id,
    })
    this._rejoin(async())
})

Kibitzer.prototype.publish = cadence(function (async, entry, internal) {
    var cookie = this.client.publish(entry, internal)
    this.cookies[cookie] = async()
    this._reactor.check()
})

Kibitzer.prototype._enqueue = cadence(function (async, post) {
    if (!this.available) {
        this.logger('info', 'enqueue', {
            kibitzerId: this.legislator.id,
            islandId: this.islandId,
            available: this.available,
            received: request.body
        })
        request.raise(517)
    }
    var response = { islandId: this.islandId, posted: false, entries: [] }
    post.entries.forEach(function (entry) {
        var outcome = this.legislator.post(entry.cookie, entry.value, entry.internal)
        // todo: I expect the cookie to be in the outcome.
        // todo: test receiving entries, enqueuing, when we are not the leader.
        if (outcome.posted) {
            response.posted = true
            response.entries.push({ cookie: entry.cookie, promise: outcome.promise })
        }
    }, this)
    this._reactor.check()
    this.logger('info', 'enqueue', {
        kibitzerId: this.legislator.id,
        islandId: this.islandId,
        available: this.available,
        received: post,
        response: response
    })
    return response
})

Kibitzer.prototype._receive = cadence(function (async, post) {
    if (!this.available) {
        this.logger('info', 'receive', {
            kibitzerId: this.legislator.id,
            islandId: this.islandId,
            available: this.available,
            received: request.body
        })
        return null
        // request.raise(517)
    }
    var route = post.route, index = post.index, expanded = serializer.expand(post.messages)
    async(function () {
        route = this.legislator.routeOf(route.path, route.pulse)
        this.legislator.inbox(route, expanded)
        if (index + 1 < route.path.length) {
            async(function () {
                var forwards = this.legislator.forwards(route, index)
                var serialized = {
                    islandId: this.islandId,
                    route: route,
                    index: index + 1,
                    messages: serializer.flatten(forwards)
                }
                this.ua.fetch(
                    this.createBinder(this.legislator.location[route.path[index + 1]])
                , {
                    url: '/receive',
                    payload: serialized
                }, async())
            }, function (body, response) {
                var returns = this._response(response, body, 'returns', 'returns', [])
                this.legislator.inbox(route, returns)
            })
        }
    }, function () {
        var returns = this.legislator.returns(route, index)
        this._reactor.check()
        this.logger('info', 'receive', {
            kibitzerId: this.legislator.id,
            islandId: this.islandId,
            available: this.available,
            received: post,
            returns: returns
        })
        return { returns: returns }
    })
})

Kibitzer.prototype.wait = function (promise, callback) {
    if (Id.compare(promise, this.client.uniform) <= 0) {
        callback()
    } else {
        var wait = this.waits.find({ promise: promise })
        if (!wait) {
            wait = { promise: promise, callbacks: [] }
            this.waits.insert(wait)
        }
        wait.callbacks.push(callback)
    }
}

Kibitzer.prototype.stop = cadence(function (async) {
    this.scheduler.workers = 0
    if (this._interval != null) {
        clearInterval(this._interval)
        this._interval = null
    }
    this.scram(async())
})

module.exports = Kibitzer
<commit_msg>Remove `prefer` function.<commit_after>var assert = require('assert')

var RBTree = require('bintrees').RBTree

var cadence = require('cadence')

var Reactor = require('reactor')

var Id = require('paxos/id')
var Legislator = require('paxos/legislator')
var Client = require('paxos/client')

var Monotonic = require('monotonic')

var Scheduler = require('happenstance')

var interrupt = require('interrupt').createInterrupter()

function Kibitzer (id, options) {
    assert(id != null, 'id is required')

    options.ping || (options.ping = 250)
    options.timeout || (options.timeout = 1000)
    this.poll = options.poll || 5000
    this.ping = options.ping
    this.timeout = options.timeout

    this.syncLength = options.syncLength || 250

    this._reactor = new Reactor({ object: this, method: '_tick' })

    this.happenstance = new Scheduler
    this._Date = options.Date || Date

    this.joining = []

    this.url = options.url
    this._ua = options.ua

    this.islandId = null
    this.baseId = id
    this.suffix = '0'

    this.legislator = this._createLegislator()
    this.client = new Client(this.legislator.id)

    this.cookies = {}
    this.logger = options.logger || function () {}

    this.discovery = options.discovery

    this.available = false

    this.waits = new RBTree(function (a, b) { return Id.compare(a.promise, b.promise) })
}

Kibitzer.prototype._createLegislator = function () {
    var suffix = this.suffix
    var words = Monotonic.parse(this.suffix)
    this.suffix = Monotonic.toString(Monotonic.increment(words))
    return new Legislator(this.baseId + suffix, {
        ping: this.ping,
        timeout: this.timeout
    })
}

Kibitzer.prototype._tick = cadence(function (async) {
    var dirty = false
    async(function () {
        var outgoing = this.client.outbox()
        if (outgoing.length) {
            var post
            async(function () {
                var url = this.legislator.location[this.legislator.government.majority[0]]
                this._ua.enqueue(url, post = {
                    entries: outgoing
                }, async())
            }, function (body) {
                var published = body ? body.entries : []
                this.logger('info', 'enqueued', {
                    kibitzerId: this.legislator.id,
                    sent: post,
                    received: body
                })
                this.client.published(published)
            })
            dirty = true
        }
    }, function () {
        async.forEach(function (route) {
            var forwards = this.legislator.forwards(route, 0), serialized
            async(function () {
                serialized = {
                    route: route,
                    index: 1,
                    messages: serializer.flatten(forwards)
                }
                var url = this.legislator.location[route.path[1]]
                this._ua.receive(url, serialized, async())
            }, function (body) {
                var returns = body ? body.returns : []
                this.logger('info', 'published', {
                    kibitzerId: this.legislator.id,
                    sent: serialized,
                    received: body
                })
                this.legislator.inbox(route, returns)
                this.legislator.sent(route, forwards, returns)
            })
            dirty = true
        })(this.legislator.outbox())
    }, function () {
        var entries = this.legislator.since(this.client.uniform)
        if (entries.length) {
            this.logger('info', 'consuming', {
                kibitzerId: this.legislator.id,
                entries: entries
            })
            var promise = this.client.receive(entries)
            async.forEach(function (entry) {
                this.logger('info', 'consume', {
                    kibitzerId: this.legislator.id,
                    entry: entry
                })
                var callback = this.cookies[entry.cookie]
                if (callback) {
                    callback(null, entry)
                    delete this.cookies[entry.cookie]
                }
                var wait
                while ((wait = this.waits.min()) && Id.compare(wait.promise, entry.promise) <= 0) {
                    wait.callbacks.forEach(function (callback) { callback() })
                    this.waits.remove(wait)
                }
            })(this.client.since(promise))
            dirty = true
        }
    }, function () {
        if (dirty) {
            this._reactor.check()
        }
    })
})

Kibitzer.prototype._checkSchedule2 = cadence(function (async) {
    async.forEach(function (event) {
        var method = 'when' + event[0].toUpperCase() + event.substring(1)
        this[method](event, async())
    })(this.happenstance.check())
})

Kibitzer.prototype._schedule = function (type, delay) {
    this.happenstance.schedule(this.legislator.id, type, this._Date.now() + delay)
}

Kibitzer.prototype._checkSchedule = function () {
    this._interval = setInterval(function () {
        if (this.legislator.checkSchedule()) {
            this.publisher.nudge()
        }
        this.scheduler.nudge()
    }.bind(this), 50)
}

Kibitzer.prototype._urls = function () {
    var urls = []
    for (var key in this.legislator.location) {
        urls.push(this.legislator.location[key])
    }
    return urls
}

Kibitzer.prototype.discover = cadence(function (async) {
    var urls = this._urls()
    if (urls.length) {
        return { id: this.legislator.id, urls: urls }
    } else {
        return null
    }
})

Kibitzer.prototype.pull = cadence(function (async, url) {
    assert(url, 'url is missing')
    var dataset = 'log', post, next = null
    var sync = async(function () {
        this._ua.sync(url, post = {
            kibitzerId: this.legislator.id,
            dataset: dataset,
            next: next
        }, async())
    }, function (body) {
        this.logger('info', 'pulled', {
            kibitzerId: this.legislator.id,
            url: url,
            sent: post,
            received: body
        })
        if (!body) {
            throw this._unexceptional(new Error('unable to sync'))
        } else {
            this._schedule('joining', this.timeout)
            switch (dataset) {
            case 'log':
                this.legislator.inject(body.entries)
                if (body.next == null) {
                    // todo: fast forward client!
                    dataset = 'meta'
                    next = null
                }
                next = body.next
                break
            case 'meta':
                this.legislator.location = body.location
                this.since = body.promise
                return [ sync.break ]
            }
        }
    })()
})

Kibitzer.prototype._sync = cadence(function (async, post) {
    if (!this.available) {
        this.logger('info', 'sync', {
            kibitzerId: this.legislator.id,
            available: this.available,
            received: request.body
        })
        interrupt.panic(new Error, 'unsyncable')
    }
    var response
    switch (post.dataset) {
    case 'log':
         response = this.legislator.extract('reverse', 24, post.next)
         break
    case 'meta':
        response = {
            // todo: oh, so this is public
            promise: this.legislator._greatestOf(this.legislator.id).uniform,
            location: this.legislator.location
        }
        break
    }
    this.logger('info', 'sync', {
        kibitzerId: this.legislator.id,
        available: this.available,
        post: post,
        response: response
    })
    return response
})

Kibitzer.prototype.join = cadence(function (async, url) {
    if (this._urls().length) {
        return []
    }
    this.joining.push(async())
    this._schedule('join', 0)
})

Kibitzer.prototype._joined = function () {
    this.joining.splice(0, this.joining.length).forEach(function (callback) { callback() })
}

Kibitzer.prototype._naturalize = cadence(function (async, body) {
    this.logger('info', 'naturalize', {
        kibitzerId: this.legislator.id,
        received: body
    })
    async(function () {
        this.pull(body.urls[0], async())
    }, function () {
        this.bootstrapped = false
        this.legislator.immigrate(this.legislator.id)
        this.legislator.initialize()
        this.client.prime(this.legislator.prime(this.since))
        assert(this.client.length, 'no entries in client')
        this._reactor.turnstile.workers = 1
        this._reactor.check()
        this._schedule('joining', this.timeout)
        this.available = true
        this.publish({
            type: 'naturalize',
            id: this.legislator.id,
            location: this.url
        }, true, async())
    }, function () {
        this._joined()
    })
})

Kibitzer.prototype._rejoin = cadence(function (async, body) {
    async(function () {
        this.available = false
        this.client.clear().forEach(function (request) {
            var callback = this.cookies[request.cookie]
            assert(callback, 'request missing callback')
            delete this.cookies[request.cookie]
            callback(this._unexceptional(new Error('rejoining')))
        }, this)
        this.scram(async())
    }, function () {
        if (body) this._naturalize(body, async())
        else this._schedule('join', 0)
    })
})

Kibitzer.prototype.bootstrap = function (async) {
    this.bootstrapped = true
    this._reactor.turnstile.workers = 1
    this.legislator.bootstrap()
    this.logger('info', 'bootstrap', {
        kibitzerId: this.legislator.id
    })
    this.client.prime(this.legislator.prime('1/0'))
    this.legislator.location[this.legislator.id] = this.url
    this.available = true
}

Kibitzer.prototype.whenJoin = cadence(function (async) {
    async(function () {
        this._ua.discover(async())
    }, function (body, okay) {
        this.logger('info', 'join', {
            okay: okay,
            kibitzerId: this.legislator.id,
            received: JSON.stringify(body)
        })
        if (okay) {
            this._naturalize(body, async())
        } else {
            this._schedule('join', this.timeout)
        }
    })
})

Kibitzer.prototype.whenJoining = cadence(function (async) {
    this.logger('info', 'joining', {
        kibitzerId: this.legislator.id,
    })
    this._rejoin(async())
})

Kibitzer.prototype.publish = cadence(function (async, entry, internal) {
    var cookie = this.client.publish(entry, internal)
    this.cookies[cookie] = async()
    this._reactor.check()
})

Kibitzer.prototype._enqueue = cadence(function (async, post) {
    if (!this.available) {
        this.logger('info', 'enqueue', {
            kibitzerId: this.legislator.id,
            islandId: this.islandId,
            available: this.available,
            received: request.body
        })
        request.raise(517)
    }
    var response = { islandId: this.islandId, posted: false, entries: [] }
    post.entries.forEach(function (entry) {
        var outcome = this.legislator.post(entry.cookie, entry.value, entry.internal)
        // todo: I expect the cookie to be in the outcome.
        // todo: test receiving entries, enqueuing, when we are not the leader.
        if (outcome.posted) {
            response.posted = true
            response.entries.push({ cookie: entry.cookie, promise: outcome.promise })
        }
    }, this)
    this._reactor.check()
    this.logger('info', 'enqueue', {
        kibitzerId: this.legislator.id,
        islandId: this.islandId,
        available: this.available,
        received: post,
        response: response
    })
    return response
})

Kibitzer.prototype._receive = cadence(function (async, post) {
    if (!this.available) {
        this.logger('info', 'receive', {
            kibitzerId: this.legislator.id,
            islandId: this.islandId,
            available: this.available,
            received: request.body
        })
        return null
        // request.raise(517)
    }
    var route = post.route, index = post.index, expanded = serializer.expand(post.messages)
    async(function () {
        route = this.legislator.routeOf(route.path, route.pulse)
        this.legislator.inbox(route, expanded)
        if (index + 1 < route.path.length) {
            async(function () {
                var forwards = this.legislator.forwards(route, index)
                var serialized = {
                    islandId: this.islandId,
                    route: route,
                    index: index + 1,
                    messages: serializer.flatten(forwards)
                }
                this.ua.fetch(
                    this.createBinder(this.legislator.location[route.path[index + 1]])
                , {
                    url: '/receive',
                    payload: serialized
                }, async())
            }, function (body, response) {
                var returns = this._response(response, body, 'returns', 'returns', [])
                this.legislator.inbox(route, returns)
            })
        }
    }, function () {
        var returns = this.legislator.returns(route, index)
        this._reactor.check()
        this.logger('info', 'receive', {
            kibitzerId: this.legislator.id,
            islandId: this.islandId,
            available: this.available,
            received: post,
            returns: returns
        })
        return { returns: returns }
    })
})

Kibitzer.prototype.wait = function (promise, callback) {
    if (Id.compare(promise, this.client.uniform) <= 0) {
        callback()
    } else {
        var wait = this.waits.find({ promise: promise })
        if (!wait) {
            wait = { promise: promise, callbacks: [] }
            this.waits.insert(wait)
        }
        wait.callbacks.push(callback)
    }
}

Kibitzer.prototype.stop = cadence(function (async) {
    this.scheduler.workers = 0
    if (this._interval != null) {
        clearInterval(this._interval)
        this._interval = null
    }
    this.scram(async())
})

module.exports = Kibitzer
<|endoftext|>


<commit_before>require('babel-register');

module.exports = {
  development: {
    client: 'sqlite3',
    connection: {
      filename: './dev-db.sqlite3'
    },
    seeds: {
      directory: './src/database/seeds'
    },
    migrations: {
      directory: './src/database/migrations'
    },
    useNullAsDefault: true
  },
  production: {
    client: 'sqlite3',
    connection: {
      filename: './prod-db.sqlite3'
    },
    seeds: {
      directory: './src/database/seeds'
    },
    migrations: {
      directory: './src/database/migrations'
    },
    useNullAsDefault: true
  },

};
<commit_msg>Fix knexfile.js due to changes in .babelrc<commit_after>require('babel-register')({ presets: ['es2015'] });

module.exports = {
  development: {
    client: 'sqlite3',
    connection: {
      filename: './dev-db.sqlite3'
    },
    seeds: {
      directory: './src/database/seeds'
    },
    migrations: {
      directory: './src/database/migrations'
    },
    useNullAsDefault: true
  },
  production: {
    client: 'sqlite3',
    connection: {
      filename: './prod-db.sqlite3'
    },
    seeds: {
      directory: './src/database/seeds'
    },
    migrations: {
      directory: './src/database/migrations'
    },
    useNullAsDefault: true
  },

};
<|endoftext|>


<commit_before>/*!
 * jQuery textarea autoResize plugin v0.2.1
 * http://github.com/thomasjo/jquery-autoresize
 *
 * Copyright (c) 2011 Thomas Johansen | https://raw.github.com/thomasjo/jquery-autoresize/master/LICENSE.txt
 */

;(function() {
    'use strict';

    var root = this,
        $ = root.$,

        defaults = {
            resize: $.noop
        };

    $.fn.autoResize = function(options) {
        var settings = $.extend({}, defaults, options);

        this.filter('textarea').each(function() {
            var $textarea = $(this).css({
                    'overflow-y': 'hidden',
                    'resize': 'none'
                }),

                minHeight = $textarea.height(),
                previousHeight = minHeight,

                $slave = (function() {
                    var $clone = $textarea.clone()
                        .attr('tab-index', -1)
                        .removeAttr('id')
                        .removeAttr('name')
                        .css({
                            'position': 'absolute',
                            'top': 0,
                            'left': -9999
                        });

                    return $clone.insertBefore($textarea);
                })(),

                adjustHeightIfNeeded = function () {
                    var text = $textarea.val(),
                        height;

                    $slave
                        .height(0)
                        .val(text)
                        .scrollTop(9999);

                    height = Math.max(minHeight, $slave.scrollTop());
                    if (height === previousHeight) {
                        return;
                    }

                    previousHeight = height;
                    settings.resize.call(this);
                    $textarea.height(height);
                };

            $textarea.unbind('.resize');

            if (supportsInputEvent()) {
                $textarea.bind('input.resize', adjustHeightIfNeeded);
            }
            else if (supportsPropertyChangedEvent()) {
                $textarea.bind('propertychanged.resize', adjustHeightIfNeeded);
            }
            else {
                $textarea.bind('keypress.resize', adjustHeightIfNeeded);
            }

            adjustHeightIfNeeded();
        });

        return this;
    };

    function supportsInputEvent() {
        if ('oninput' in document.body) {
            return true;
        }

        document.body.setAttribute('oninput', 'return');
        return typeof document.body.oninput === 'function';
    }

    function supportsPropertyChangedEvent() {
        return 'onpropertychanged' in document.body;
    }

}).call(this);
<commit_msg>Fixed multiple textareas autoresize in older IEs<commit_after>/*!
 * jQuery textarea autoResize plugin v0.2.1
 * http://github.com/thomasjo/jquery-autoresize
 *
 * Copyright (c) 2011 Thomas Johansen | https://raw.github.com/thomasjo/jquery-autoresize/master/LICENSE.txt
 */

;(function() {
    'use strict';

    var root = this,
        $ = root.$,

        defaults = {
            resize: $.noop
        };

    $.fn.autoResize = function(options) {
        var settings = $.extend({}, defaults, options);

        this.filter('textarea').each(function() {
            var $textarea = $(this).css({
                    'overflow-y': 'hidden',
                    'resize': 'none'
                }),

                minHeight = $textarea.height(),
                previousHeight = minHeight,

                $slave = (function() {
                    var $clone = $textarea.clone()
                        .attr('tabindex', -1)
                        .removeAttr('id')
                        .removeAttr('name')
                        .css({
                            'position': 'absolute',
                            'top': 0,
                            'left': -9999
                        });

                    return $clone.insertBefore($textarea);
                })(),

                adjustHeightIfNeeded = function () {
                    var text = $textarea.val(),
                        height;

                    $slave
                        .width($textarea.width())
                        .height(0)
                        .val(text)
                        .scrollTop(9999);

                    height = Math.max(minHeight, $slave.scrollTop());
                    if (height === previousHeight) {
                        return;
                    }

                    previousHeight = height;
                    settings.resize.call(this);
                    $textarea.height(height);
                };

            $textarea.unbind('.resize');

            if (supportsInputEvent()) {
                $textarea.bind('input.resize', adjustHeightIfNeeded);
            }
            else if (supportsPropertyChangeEvent()) {
                $textarea.bind('propertychange.resize', adjustHeightIfNeeded);
            }
            else {
                $textarea.bind('keypress.resize', adjustHeightIfNeeded);
            }

            adjustHeightIfNeeded();
        });

        return this;
    };

    function supportsInputEvent() {
        if ('oninput' in document.body) {
            return true;
        }

        document.body.setAttribute('oninput', 'return');

        var supports = typeof document.body.oninput === 'function';
        delete document.body.oninput;
        return supports;
    }

    function supportsPropertyChangeEvent() {
        return 'onpropertychange' in document.body;
    }

}).call(this);
<|endoftext|>


<commit_before>/*!
	Copyright 2010 Mal Curtis
*/

if (typeof jQuery == 'undefined') throw ("jQuery Required");

(function($){
	// Public General Plugin methods $.DirtyForms
	$.extend({
		DirtyForms: {
			debug : false,
			message : 'You\'ve made changes on this page which aren\'t saved. If you leave you will lose these changes.',
			title : 'Are you sure you want to do that?',
			dirtyClass : 'dirty',
			listeningClass : 'dirtylisten',
			ignoreClass : 'ignoredirty',
			helpers : [],
			dialog : {
				refire : function(content, ev){
					$.facebox(content);
				},
				fire : function(message, title){
					var content = '
' + title + '

' + message + '

ContinueStop';
					$.facebox(content);
				},
				bind : function(){
					var close = function(decision) {
						return function(e) {
							e.preventDefault();
							$(document).trigger('close.facebox');
							decision(e);
						};
					};
					$('#facebox .cancel, #facebox .close, #facebox_overlay').click(close(decidingCancel));
					$('#facebox .continue').click(close(decidingContinue));
				},
				stash : function(){
					var fb = $('#facebox');
					return ($.trim(fb.html()) == '' || fb.css('display') != 'block') ?
					   false :
					   $('#facebox .content').clone(true);
				},
				selector : '#facebox .content'
			},

			isDirty : function(){
				dirtylog('Core isDirty is starting ');
				var isDirty = false;
				if (settings.disabled) return false;
				$(':dirtylistening').each(function(){
					if($(this).isDirty()){
						isDirty = true;
						return true;
					}
				});

				$.each($.DirtyForms.helpers, function(key,obj){
					$('form').each(function(i,node) {
						if("isDirty" in obj){
							if(obj.isDirty(node)){
								isDirty = true;
								return true;
							}
						}
						// For backward compatibility, we call isNodeDirty (deprecated)
						if("isNodeDirty" in obj){
							if(obj.isNodeDirty(node)) {
								isDirty = true;
								return true;
							}
						}
					});

				});

				dirtylog('Core isDirty is returning ' + isDirty);
				return isDirty;
			},

			disable : function(){
				settings.disabled = true;
			},

			isDeciding : function(){
				return settings.deciding;
			},

			decidingContinue : function(e){
				decidingContinue(e);
			},

			decidingCancel : function(e){
				decidingCancel(e);
			},

			dirtylog : function(msg){
				dirtylog(msg);
			}
		}
	});

	// Create a custom selector $('form:dirty')
	$.extend($.expr[":"], {
		dirtylistening : function(a){
			return $(a).hasClass($.DirtyForms.listeningClass);
		},
		dirty : function(a){
			return $(a).hasClass($.DirtyForms.dirtyClass);
		}
	});

	// NEW Public Element methods ( $('form').dirtyForms('methodName', params) )
	var methods = {
		init : function() {
			var core = $.DirtyForms;

			dirtylog('Adding forms to watch');
			bindExit();

			return this.each(function(e){
				dirtylog('Adding form ' + $(this).attr('id') + ' to forms to watch');
				$(this).addClass(core.listeningClass);
				// include all inputs HTML 5 and future included
				$(this).find('input, textarea')
					// exclude HTML 4 but text and password, but include HTML 5 except search
					.not("input[type='checkbox'],input[type='radio'],input[type='button']," +
						"input[type='image'],input[type='submit'],input[type='reset']," +
						"input[type='file'],input[type='hidden'],input[type='search']")
					.focus(onFocus);
				$(this).find("input[type='checkbox'],input[type='radio'],select").change(onCheckedChange);
			});
		},
		// Returns true if any of the supplied elements are dirty
		isDirty : function() {
			var isDirty = false;
			var node = this;
			if (focusedIsDirty()) {
				isDirty = true;
				return true;
			}
			this.each(function(e){
				if($(this).hasClass($.DirtyForms.dirtyClass)){
					isDirty = true;
					return true;
				}
			});
			$.each($.DirtyForms.helpers, function(key,obj){
				if("isDirty" in obj){
					if(obj.isDirty(node)){
						isDirty = true;
						return true;
					}
				}
				// For backward compatibility, we call isNodeDirty (deprecated)
				if("isNodeDirty" in obj){
					if(obj.isNodeDirty(node)){
						isDirty = true;
						return true;
					}
				}
			});

			dirtylog('isDirty returned ' + isDirty);
			return isDirty;
		},
		// Marks the element(s) that match the selector dirty
		setDirty : function() {
			dirtylog('setDirty called');
			return this.each(function(e){
				$(this).addClass($.DirtyForms.dirtyClass).parents('form').addClass($.DirtyForms.dirtyClass);
			});
		},
		// "Cleans" this dirty form by essentially forgetting that it is dirty
		setClean : function() {
			dirtylog('setClean called');
			settings.focused = {element: false, value: false};

			return this.each(function(e){
				var node = this;
				$(this).removeClass($.DirtyForms.dirtyClass).parents('form').removeClass($.DirtyForms.dirtyClass).find(':dirty').removeClass($.DirtyForms.dirtyClass);

				// Clean helpers
				$.each($.DirtyForms.helpers, function(key,obj){
					if("setClean" in obj){
						obj.setClean(node);
					}
				});
			});
		}

		// ADD NEW METHODS HERE
	};

	$.fn.dirtyForms = function(method) {
		// Method calling logic
		if ( methods[method] ) {
			return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
		} else if ( typeof method === 'object' || ! method ) {
			return methods.init.apply( this, arguments );
		} else {
			$.error( 'Method ' +  method + ' does not exist on jQuery.tooltip' );
		}
	};

	// Deprecated Methods for Backward Compatibility
	// DO NOT ADD MORE METHODS LIKE THESE, ADD METHODS WHERE INDICATED ABOVE
	$.fn.setDirty = function(){
		return this.dirtyForms('setDirty');
	}
	$.fn.isDirty = function(){
		return this.dirtyForms('isDirty');
	}
    $.fn.cleanDirty = function(){
		return this.dirtyForms('setClean');
    }

	// Private Properties and Methods
	var settings = $.DirtyForms = $.extend({
		disabled : false,
		exitBound : false,
		formStash : false,
		dialogStash : false,
		deciding : false,
		decidingEvent : false,
		currentForm : false,
		hasFirebug : "console" in window && "firebug" in window.console,
		hasConsoleLog: "console" in window && "log" in window.console,
		focused: {"element": false, "value": false}
	}, $.DirtyForms);

	var onCheckedChange = function() {
		$(this).dirtyForms('setDirty');
	}

	var onFocus = function() {
		element = $(this);
		if (focusedIsDirty()) {
			element.dirtyForms('setDirty');
		}
		settings.focused['element'] = element;
		settings.focused['value']	= element.val();
	}
	var focusedIsDirty = function() {
		/** Check, whether the value of focused element has changed */
		return settings.focused["element"] &&
			(settings.focused["element"].val() !== settings.focused["value"]);
	}

	var dirtylog = function(msg){
		if(!$.DirtyForms.debug) return;
		msg = "[DirtyForms] " + msg;
		settings.hasFirebug ?
			console.log(msg) :
			settings.hasConsoleLog ?
				window.console.log(msg) :
				alert(msg);
	}
	var bindExit = function(){
		if(settings.exitBound) return;

    // We need a separate set of processes for when the form is
    // running inside of an iframe. We need the livaquery library
    // in order to dynamically bind to elements within the iframe.
    if (top !== window && $.livequery) {
      $('a').livequery('click', aBindFn);
      $('form').livequery('submit', formBindFn);
      $(top.document).contents().find('a').bind('click', aBindFn);
      $(top.window).bind('beforeunload', beforeunloadBindFn);
    }
    else {
      $('a').live('click',aBindFn);
      $('form').live('submit',formBindFn);
    }
		$(window).bind('beforeunload', beforeunloadBindFn);

		settings.exitBound = true;
	}

	var aBindFn = function(ev){
		 bindFn(ev);
	}

	var formBindFn = function(ev){
		settings.currentForm = this;
		bindFn(ev);
	}

	var beforeunloadBindFn = function(ev){
		var result = bindFn(ev);

		if(result && settings.doubleunloadfix != true){
			dirtylog('Before unload will be called, resetting');
			settings.deciding = false;
		}

		settings.doubleunloadfix = true;
		setTimeout(function(){settings.doubleunloadfix = false;},200);

		// Bug Fix: Only return the result if it is a string,
		// otherwise don't return anything.
		if (typeof(result) == 'string'){
			ev = ev || window.event;

			// For IE and Firefox prior to version 4
			if (ev) {
				ev.returnValue = result;
			}

			// For Safari
			return result;
		}
	}

	var bindFn = function(ev){
		dirtylog('Entering: Leaving Event fired, type: ' + ev.type + ', element: ' + ev.target + ', class: ' + $(ev.target).attr('class') + ' and id: ' + ev.target.id);

		if(ev.type == 'beforeunload' && settings.doubleunloadfix){
			dirtylog('Skip this unload, Firefox bug triggers the unload event multiple times');
			settings.doubleunloadfix = false;
			return false;
		}

		if($(ev.target).hasClass(settings.ignoreClass) || isDifferentTarget(ev)){
			dirtylog('Leaving: Element has ignore class or has target=\'_blank\'');
			if(!ev.isDefaultPrevented()){
				clearUnload();
			}
			return false;
		}

		if(settings.deciding){
			dirtylog('Leaving: Already in the deciding process');
			return false;
		}

		if(ev.isDefaultPrevented()){
			dirtylog('Leaving: Event has been stopped elsewhere');
			return false;
		}

		if(!settings.isDirty()){
			dirtylog('Leaving: Not dirty');
			if(!ev.isDefaultPrevented()){
				clearUnload();
			}
			return false;
		}

		if(ev.type == 'submit' && $(ev.target).dirtyForms('isDirty')){
			dirtylog('Leaving: Form submitted is a dirty form');
			if(!ev.isDefaultPrevented()){
				clearUnload();
			}
			return true;
		}

		settings.deciding = true;
		settings.decidingEvent = ev;
		dirtylog('Setting deciding active');

		if(settings.dialog !== false)
		{
			dirtylog('Saving dialog content');
			settings.dialogStash =settings.dialog.stash();
			dirtylog(settings.dialogStash);
		}

		// Callback for page access in current state
		$(document).trigger('defer.dirtyforms');

		if(ev.type == 'beforeunload'){
			//clearUnload();
			dirtylog('Returning to beforeunload browser handler with: ' + settings.message);
			return settings.message;
		}
		if(!settings.dialog) return;

		ev.preventDefault();
		ev.stopImmediatePropagation();

		if($(ev.target).is('form') && $(ev.target).parents(settings.dialog.selector).length > 0){
			dirtylog('Stashing form');
			settings.formStash = $(ev.target).clone(true).hide();
		}else{
			settings.formStash = false;
		}

		dirtylog('Deferring to the dialog');
		settings.dialog.fire($.DirtyForms.message, $.DirtyForms.title);
		settings.dialog.bind();
	}

	var isDifferentTarget = function(ev){
		var aTarget = $(ev.target).attr('target');
		if (typeof aTarget === 'string') {
			aTarget = aTarget.toLowerCase();
		}
		return (aTarget === '_blank');
	}

	var decidingCancel = function(ev){
		ev.preventDefault();
		$(document).trigger('decidingcancelled.dirtyforms');
		if(settings.dialog !== false && settings.dialogStash !== false)
		{
			dirtylog('Refiring the dialog with stashed content');
			settings.dialog.refire(settings.dialogStash.html(), ev);
		}
		$(document).trigger('decidingcancelledAfter.dirtyforms');
		settings.dialogStash = false;
		settings.deciding = settings.currentForm = settings.decidingEvent = false;
	}

	var decidingContinue = function(ev){
		window.onbeforeunload = null; // fix for chrome
		ev.preventDefault();
		settings.dialogStash = false;
		$(document).trigger('decidingcontinued.dirtyforms');
		refire(settings.decidingEvent);
		settings.deciding = settings.currentForm = settings.decidingEvent = false;
	}

	var clearUnload = function(){
		// I'd like to just be able to unbind this but there seems
		// to be a bug in jQuery which doesn't unbind onbeforeunload
		dirtylog('Clearing the beforeunload event');
		$(window).unbind('beforeunload', beforeunloadBindFn);
		window.onbeforeunload = null;
	}

	var refire = function(e){
		$(document).trigger('beforeRefire.dirtyforms');
		switch(e.type){
			case 'click':
				dirtylog("Refiring click event");
				var event = new jQuery.Event('click');
				$(e.target).trigger(event);
				if(!event.isDefaultPrevented()){
					var anchor = $(e.target).closest('[href]');
					dirtylog('Sending location to ' + anchor.attr('href'));
					location.href = anchor.attr('href');
					return;
				}
				break;
			default:
				dirtylog("Refiring " + e.type + " event on " + e.target);
				var target;
				if(settings.formStash){
					dirtylog('Appending stashed form to body');
					target = settings.formStash;
					$('body').append(target);
				}
				else{
					target = $(e.target);
					if(!target.is('form'))
						target = target.closest('form');
				}
				target.trigger(e.type);
				break;
		}
	}

})(jQuery);
<commit_msg>Consolidated isDirty logic (issue #26), general housekeeping<commit_after>/*!
	Copyright 2010 Mal Curtis
*/

if (typeof jQuery == 'undefined') throw ("jQuery Required");

(function($){
	// Public General Plugin methods $.DirtyForms
	$.extend({
		DirtyForms: {
			debug : false,
			message : 'You\'ve made changes on this page which aren\'t saved. If you leave you will lose these changes.',
			title : 'Are you sure you want to do that?',
			dirtyClass : 'dirty',
			listeningClass : 'dirtylisten',
			ignoreClass : 'ignoredirty',
			helpers : [],
			dialog : {
				refire : function(content, ev){
					$.facebox(content);
				},
				fire : function(message, title){
					var content = '
' + title + '

' + message + '

ContinueStop';
					$.facebox(content);
				},
				bind : function(){
					var close = function(decision) {
						return function(e) {
							e.preventDefault();
							$(document).trigger('close.facebox');
							decision(e);
						};
					};
					$('#facebox .cancel, #facebox .close, #facebox_overlay').click(close(decidingCancel));
					$('#facebox .continue').click(close(decidingContinue));
				},
				stash : function(){
					var fb = $('#facebox');
					return ($.trim(fb.html()) == '' || fb.css('display') != 'block') ?
					   false :
					   $('#facebox .content').clone(true);
				},
				selector : '#facebox .content'
			},

			isDirty : function(){
				return $(':dirtylistening').dirtyForms('isDirty');
			},

			disable : function(){
				settings.disabled = true;
			},

			isDeciding : function(){
				return settings.deciding;
			},

			decidingContinue : function(e){
				decidingContinue(e);
			},

			decidingCancel : function(e){
				decidingCancel(e);
			},

			dirtylog : function(msg){
				dirtylog(msg);
			}
		}
	});

	// Create a custom selector $('form:dirty')
	$.extend($.expr[":"], {
		dirtylistening : function(a){
			return $(a).hasClass($.DirtyForms.listeningClass);
		},
		dirty : function(a){
			return $(a).hasClass($.DirtyForms.dirtyClass);
		}
	});

	// Public Element methods ( $('form').dirtyForms('methodName', args) )
	var methods = {
		init : function() {
			var core = $.DirtyForms;

			dirtylog('Adding forms to watch');
			bindExit();

			return this.each(function(e){
				dirtylog('Adding form ' + $(this).attr('id') + ' to forms to watch');
				$(this).addClass(core.listeningClass);
				// include all inputs, HTML 5 and future included
				$(this).find('input, textarea')
					// exclude all HTML 4 except text and password, but include HTML 5 except search
					.not("input[type='checkbox'],input[type='radio'],input[type='button']," +
						"input[type='image'],input[type='submit'],input[type='reset']," +
						"input[type='file'],input[type='hidden'],input[type='search']")
					.focus(onFocus);
				$(this).find("input[type='checkbox'],input[type='radio'],select").change(onSelectionChange);
			});
		},
		// Returns true if any of the supplied elements are dirty
		isDirty : function() {
			var isDirty = false;
			var node = this;
			if (settings.disabled) return false;
			if (focusedIsDirty()) {
				isDirty = true;
				return true;
			}
			this.each(function(e){
				if($(this).hasClass($.DirtyForms.dirtyClass)){
					isDirty = true;
					return true;
				}
			});
			$.each($.DirtyForms.helpers, function(key,obj){
				if("isDirty" in obj){
					if(obj.isDirty(node)){
						isDirty = true;
						return true;
					}
				}
				// For backward compatibility, we call isNodeDirty (deprecated)
				if("isNodeDirty" in obj){
					if(obj.isNodeDirty(node)){
						isDirty = true;
						return true;
					}
				}
			});

			dirtylog('isDirty returned ' + isDirty);
			return isDirty;
		},
		// Marks the element(s) that match the selector dirty
		setDirty : function() {
			dirtylog('setDirty called');
			return this.each(function(e){
				$(this).addClass($.DirtyForms.dirtyClass).parents('form').addClass($.DirtyForms.dirtyClass);
			});
		},
		// "Cleans" this dirty form by essentially forgetting that it is dirty
		setClean : function() {
			dirtylog('setClean called');
			settings.focused = {element: false, value: false};

			return this.each(function(e){
				var node = this;
				$(this).removeClass($.DirtyForms.dirtyClass).parents('form').removeClass($.DirtyForms.dirtyClass).find(':dirty').removeClass($.DirtyForms.dirtyClass);

				// Clean helpers
				$.each($.DirtyForms.helpers, function(key,obj){
					if("setClean" in obj){
						obj.setClean(node);
					}
				});
			});
		}

		// ADD NEW METHODS HERE
	};

	$.fn.dirtyForms = function(method) {
		// Method calling logic
		if ( methods[method] ) {
			return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
		} else if ( typeof method === 'object' || ! method ) {
			return methods.init.apply( this, arguments );
		} else {
			$.error( 'Method ' +  method + ' does not exist on jQuery.dirtyForms' );
		}
	};

	// Deprecated Methods for Backward Compatibility
	// DO NOT ADD MORE METHODS LIKE THESE, ADD METHODS WHERE INDICATED ABOVE
	$.fn.setDirty = function(){
		return this.dirtyForms('setDirty');
	}
	$.fn.isDirty = function(){
		return this.dirtyForms('isDirty');
	}
    $.fn.cleanDirty = function(){
		return this.dirtyForms('setClean');
    }

	// Private Properties and Methods
	var settings = $.DirtyForms = $.extend({
		disabled : false,
		exitBound : false,
		formStash : false,
		dialogStash : false,
		deciding : false,
		decidingEvent : false,
		currentForm : false,
		hasFirebug : "console" in window && "firebug" in window.console,
		hasConsoleLog: "console" in window && "log" in window.console,
		focused: {"element": false, "value": false}
	}, $.DirtyForms);

	var onSelectionChange = function() {
		$(this).dirtyForms('setDirty');
	}

	var onFocus = function() {
		element = $(this);
		if (focusedIsDirty()) {
			element.dirtyForms('setDirty');
		}
		settings.focused['element'] = element;
		settings.focused['value']	= element.val();
	}
	var focusedIsDirty = function() {
		// Check, whether the value of focused element has changed
		return settings.focused["element"] &&
			(settings.focused["element"].val() !== settings.focused["value"]);
	}

	var dirtylog = function(msg){
		if(!$.DirtyForms.debug) return;
		msg = "[DirtyForms] " + msg;
		settings.hasFirebug ?
			console.log(msg) :
			settings.hasConsoleLog ?
				window.console.log(msg) :
				alert(msg);
	}
	var bindExit = function(){
		if(settings.exitBound) return;

		// We need a separate set of processes for when the form is
		// running inside of an iframe. We need the livequery library
		// in order to dynamically bind to elements within the iframe.
		if (top !== window && $.livequery) {
			$('a').livequery('click', aBindFn);
			$('form').livequery('submit', formBindFn);
			$(top.document).contents().find('a').bind('click', aBindFn);
			$(top.window).bind('beforeunload', beforeunloadBindFn);
		} else {
			$('a').live('click',aBindFn);
			$('form').live('submit',formBindFn);
		}
		$(window).bind('beforeunload', beforeunloadBindFn);

		settings.exitBound = true;
	}

	var aBindFn = function(ev){
		 bindFn(ev);
	}

	var formBindFn = function(ev){
		settings.currentForm = this;
		bindFn(ev);
	}

	var beforeunloadBindFn = function(ev){
		var result = bindFn(ev);

		if(result && settings.doubleunloadfix != true){
			dirtylog('Before unload will be called, resetting');
			settings.deciding = false;
		}

		settings.doubleunloadfix = true;
		setTimeout(function(){settings.doubleunloadfix = false;},200);

		// Bug Fix: Only return the result if it is a string,
		// otherwise don't return anything.
		if (typeof(result) == 'string'){
			ev = ev || window.event;

			// For IE and Firefox prior to version 4
			if (ev) {
				ev.returnValue = result;
			}

			// For Safari
			return result;
		}
	}

	var bindFn = function(ev){
		dirtylog('Entering: Leaving Event fired, type: ' + ev.type + ', element: ' + ev.target + ', class: ' + $(ev.target).attr('class') + ' and id: ' + ev.target.id);

		if(ev.type == 'beforeunload' && settings.doubleunloadfix){
			dirtylog('Skip this unload, Firefox bug triggers the unload event multiple times');
			settings.doubleunloadfix = false;
			return false;
		}

		if($(ev.target).hasClass(settings.ignoreClass) || isDifferentTarget(ev)){
			dirtylog('Leaving: Element has ignore class or has target=\'_blank\'');
			if(!ev.isDefaultPrevented()){
				clearUnload();
			}
			return false;
		}

		if(settings.deciding){
			dirtylog('Leaving: Already in the deciding process');
			return false;
		}

		if(ev.isDefaultPrevented()){
			dirtylog('Leaving: Event has been stopped elsewhere');
			return false;
		}

		if(!settings.isDirty()){
			dirtylog('Leaving: Not dirty');
			if(!ev.isDefaultPrevented()){
				clearUnload();
			}
			return false;
		}

		if(ev.type == 'submit' && $(ev.target).dirtyForms('isDirty')){
			dirtylog('Leaving: Form submitted is a dirty form');
			if(!ev.isDefaultPrevented()){
				clearUnload();
			}
			return true;
		}

		settings.deciding = true;
		settings.decidingEvent = ev;
		dirtylog('Setting deciding active');

		if(settings.dialog !== false)
		{
			dirtylog('Saving dialog content');
			settings.dialogStash =settings.dialog.stash();
			dirtylog(settings.dialogStash);
		}

		// Callback for page access in current state
		$(document).trigger('defer.dirtyforms');

		if(ev.type == 'beforeunload'){
			//clearUnload();
			dirtylog('Returning to beforeunload browser handler with: ' + settings.message);
			return settings.message;
		}
		if(!settings.dialog) return;

		ev.preventDefault();
		ev.stopImmediatePropagation();

		if($(ev.target).is('form') && $(ev.target).parents(settings.dialog.selector).length > 0){
			dirtylog('Stashing form');
			settings.formStash = $(ev.target).clone(true).hide();
		}else{
			settings.formStash = false;
		}

		dirtylog('Deferring to the dialog');
		settings.dialog.fire($.DirtyForms.message, $.DirtyForms.title);
		settings.dialog.bind();
	}

	var isDifferentTarget = function(ev){
		var aTarget = $(ev.target).attr('target');
		if (typeof aTarget === 'string') {
			aTarget = aTarget.toLowerCase();
		}
		return (aTarget === '_blank');
	}

	var decidingCancel = function(ev){
		ev.preventDefault();
		$(document).trigger('decidingcancelled.dirtyforms');
		if(settings.dialog !== false && settings.dialogStash !== false)
		{
			dirtylog('Refiring the dialog with stashed content');
			settings.dialog.refire(settings.dialogStash.html(), ev);
		}
		$(document).trigger('decidingcancelledAfter.dirtyforms');
		settings.dialogStash = false;
		settings.deciding = settings.currentForm = settings.decidingEvent = false;
	}

	var decidingContinue = function(ev){
		window.onbeforeunload = null; // fix for chrome
		ev.preventDefault();
		settings.dialogStash = false;
		$(document).trigger('decidingcontinued.dirtyforms');
		refire(settings.decidingEvent);
		settings.deciding = settings.currentForm = settings.decidingEvent = false;
	}

	var clearUnload = function(){
		// I'd like to just be able to unbind this but there seems
		// to be a bug in jQuery which doesn't unbind onbeforeunload
		dirtylog('Clearing the beforeunload event');
		$(window).unbind('beforeunload', beforeunloadBindFn);
		window.onbeforeunload = null;
	}

	var refire = function(e){
		$(document).trigger('beforeRefire.dirtyforms');
		switch(e.type){
			case 'click':
				dirtylog("Refiring click event");
				var event = new jQuery.Event('click');
				$(e.target).trigger(event);
				if(!event.isDefaultPrevented()){
					var anchor = $(e.target).closest('[href]');
					dirtylog('Sending location to ' + anchor.attr('href'));
					location.href = anchor.attr('href');
					return;
				}
				break;
			default:
				dirtylog("Refiring " + e.type + " event on " + e.target);
				var target;
				if(settings.formStash){
					dirtylog('Appending stashed form to body');
					target = settings.formStash;
					$('body').append(target);
				}
				else{
					target = $(e.target);
					if(!target.is('form'))
						target = target.closest('form');
				}
				target.trigger(e.type);
				break;
		}
	}

})(jQuery);
<|endoftext|>


<commit_before>/**
 * jQuery.fuzzyMatch.js, version 0.2 (2011-06-22)
 *
 * https://github.com/rapportive-oss/jquery-fuzzymatch
 *
 * A fuzzy string-matching plugin for autocompleting in jQuery,
 *  based on LiquidMetal    http://github.com/rmm5t/liquidmetal/blob/master/liquidmetal.js
 *           quicksilver.js http://code.google.com/p/rails-oceania/source/browse/lachiecox/qs_score/trunk/qs_score.js
 *           QuickSilver    http://code.google.com/p/blacktree-alchemy/source/browse/trunk/Crucible/Code/NSString_BLTRExtensions.m#61
 *           FuzzyString    https://github.com/dcparker/jquery_plugins/blob/master/fuzzy-string/fuzzy-string.js
 *
 * Copyright (c) 2011, Conrad Irwin (conrad@rapportive.com)
 * Licensed under the MIT: http://www.opensource.org/licenses/mit-license.php
 *
 * TODO: Tweak heuristics, typo correction support?
**/
(function ($) {

    // The scores are arranged so that a continuous match of characters will
    // result in a total score of 1.
    //
    // The best case, this character is a match, and either this is the start
    // of the string, or the previous character was also a match.
    var SCORE_CONTINUE_MATCH = 1,

        // A new match at the start of a word scores better than a new match
        // elsewhere as it's more likely that the user will type the starts
        // of fragments.
        // (Our notion of word includes CamelCase and hypen-separated, etc.)
        SCORE_START_WORD = 0.9,

        // Any other match isn't ideal, but it's probably ok.
        SCORE_OK = 0.8,

        // The goodness of a match should decay slightly with each missing
        // character.
        //
        // i.e. "bad" is more likely than "bard" when "bd" is typed.
        //
        // This will not change the order of suggestions based on SCORE_* until
        // 100 characters are inserted between matches.
        PENALTY_SKIPPED = 0.999,

        // The goodness of an exact-case match should be higher than a
        // case-insensitive match by a small amount.
        //
        // i.e. "HTML" is more likely than "haml" when "HM" is typed.
        //
        // This will not change the order of suggestions based on SCORE_* until
        // 1000 characters are inserted between matches.
        PENALTY_CASE_MISMATCH = 0.9999,

        // If the word has more characters than the user typed, it should
        // be penalised slightly.
        //
        // i.e. "html" is more likely than "html5" if I type "html".
        //
        // However, it may well be the case that there's a sensible secondary
        // ordering (like alphabetical) that it makes sense to rely on when
        // there are many prefix matches, so we don't make the penalty increase
        // with the number of tokens.
        PENALTY_NOT_COMPLETE = 0.99;

    /**
     * Generates all possible split objects by splitting a string around a
     * character in as many ways as possible.
     *
     * @param string The string to split
     * @param char   A character on which to split it.
     *
     * @return [{
     *   before: The fragment of the string before this occurance of the
     *           character.
     *
     *   char: The original coy of this character (which may differ in case
     *         from the "char" parameter).
     *
     *   after: The fragment of the string after the occurance of the character.
     * }]
    **/
    function allCaseInsensitiveSplits(string, chr) {
        var lower = string.toLowerCase(),
            lchr = chr.toLowerCase(),

            i = lower.indexOf(lchr),
            result = [];

        while (i > -1) {
            result.push({
                before: string.slice(0, i),
                chr: string.charAt(i),
                after: string.slice(i + 1)
            });

            i = lower.indexOf(lchr, i + 1);
        }
        return result;
    }

    /**
     * Generates a case-insensitive match of the abbreviation against the string
     *
     * @param string, a canonical string to be matched against.
     * @param abbreviation, an abbreviation that a user may have typed
     *                      in order to specify that string.
     *
     * @cache (private), a cache that reduces the expected running time of the
     *                   algorithm in the case there are many repeated characters.
     *
     * @return {
     *    score:  A score (0 <= score <= 1) that indicates how likely it is that
     *            the abbreviation matches the string.
     *
     *            The score is 0 if the characters in the abbreviation do not
     *            all appear in order in the string.
     *
     *            The score is 1 if the user typed the exact string.
     *
     *            Scores are designed to be comparable when many different
     *            strings are matched against the same abbreviation, for example
     *            for autocompleting.
     *
     *    html:   A copy of the input string html-escaped, with matching letters
     *            surrounded by <b> and </b>.
     *
     * }
    **/
    $.fuzzyMatch = function (string, abbreviation, cache) {
        if (abbreviation === "") {
            return {
                score: string === "" ? SCORE_CONTINUE_MATCH : PENALTY_NOT_COMPLETE,
                html: $('<div>').text(string).html()
            };
        }

        if (cache && cache[string] && cache[string][abbreviation]) {
            return $.extend({}, cache[string][abbreviation]);
        }

        cache = cache || {};
        cache[string] = cache[string] || {};
        cache[string][abbreviation] =

           $(allCaseInsensitiveSplits(string, abbreviation.charAt(0)))
            .map(function (i, split) {
                var result = $.fuzzyMatch(split.after, abbreviation.slice(1), cache),
                    preceding_char = split.before.charAt(split.before.length - 1);

                if (split.before === "") {
                    result.score *= SCORE_CONTINUE_MATCH;

                } else if (preceding_char.match(/[\\\/\-_+.# \t"@\[\(\{&]/) ||
                        (split.chr.toLowerCase() !== split.chr && preceding_char.toLowerCase() === preceding_char)) {

                    result.score *= SCORE_START_WORD;
                } else {
                    result.score *= SCORE_OK;
                }

                if (split.chr !== abbreviation.charAt(0)) {
                    result.score *= PENALTY_CASE_MISMATCH;
                }

                result.score *= Math.pow(PENALTY_SKIPPED, split.before.length);
                result.html = $('<div>').text(split.before).append($('<b>').text(split.chr)).append(result.html).html();

                return result;
            })
            .sort(function (a, b) {
                return a.score < b.score ? 1 : a.score === b.score ? 0 : -1;
            })[0] ||

            // No matches for the next character in the abbreviation, abort!
            {
                score: 0, // This 0 will multiply up to the top, giving a total of 0
                html: $('<div>').text(string).html()
            };

        return $.extend({}, cache[string][abbreviation]);
    };
/*global jQuery */
}(jQuery));
<commit_msg>Use a custom htmlEscape for speed.<commit_after>/**
 * jQuery.fuzzyMatch.js, version 0.2 (2011-06-22)
 *
 * https://github.com/rapportive-oss/jquery-fuzzymatch
 *
 * A fuzzy string-matching plugin for autocompleting in jQuery,
 *  based on LiquidMetal    http://github.com/rmm5t/liquidmetal/blob/master/liquidmetal.js
 *           quicksilver.js http://code.google.com/p/rails-oceania/source/browse/lachiecox/qs_score/trunk/qs_score.js
 *           QuickSilver    http://code.google.com/p/blacktree-alchemy/source/browse/trunk/Crucible/Code/NSString_BLTRExtensions.m#61
 *           FuzzyString    https://github.com/dcparker/jquery_plugins/blob/master/fuzzy-string/fuzzy-string.js
 *
 * Copyright (c) 2011, Conrad Irwin (conrad@rapportive.com)
 * Licensed under the MIT: http://www.opensource.org/licenses/mit-license.php
 *
 * TODO: Tweak heuristics, typo correction support?
**/
(function ($) {

    // The scores are arranged so that a continuous match of characters will
    // result in a total score of 1.
    //
    // The best case, this character is a match, and either this is the start
    // of the string, or the previous character was also a match.
    var SCORE_CONTINUE_MATCH = 1,

        // A new match at the start of a word scores better than a new match
        // elsewhere as it's more likely that the user will type the starts
        // of fragments.
        // (Our notion of word includes CamelCase and hypen-separated, etc.)
        SCORE_START_WORD = 0.9,

        // Any other match isn't ideal, but it's probably ok.
        SCORE_OK = 0.8,

        // The goodness of a match should decay slightly with each missing
        // character.
        //
        // i.e. "bad" is more likely than "bard" when "bd" is typed.
        //
        // This will not change the order of suggestions based on SCORE_* until
        // 100 characters are inserted between matches.
        PENALTY_SKIPPED = 0.999,

        // The goodness of an exact-case match should be higher than a
        // case-insensitive match by a small amount.
        //
        // i.e. "HTML" is more likely than "haml" when "HM" is typed.
        //
        // This will not change the order of suggestions based on SCORE_* until
        // 1000 characters are inserted between matches.
        PENALTY_CASE_MISMATCH = 0.9999,

        // If the word has more characters than the user typed, it should
        // be penalised slightly.
        //
        // i.e. "html" is more likely than "html5" if I type "html".
        //
        // However, it may well be the case that there's a sensible secondary
        // ordering (like alphabetical) that it makes sense to rely on when
        // there are many prefix matches, so we don't make the penalty increase
        // with the number of tokens.
        PENALTY_NOT_COMPLETE = 0.99;

    /**
     * Generates all possible split objects by splitting a string around a
     * character in as many ways as possible.
     *
     * @param string The string to split
     * @param char   A character on which to split it.
     *
     * @return [{
     *   before: The fragment of the string before this occurance of the
     *           character.
     *
     *   char: The original coy of this character (which may differ in case
     *         from the "char" parameter).
     *
     *   after: The fragment of the string after the occurance of the character.
     * }]
    **/
    function allCaseInsensitiveSplits(string, chr) {
        var lower = string.toLowerCase(),
            lchr = chr.toLowerCase(),

            i = lower.indexOf(lchr),
            result = [];

        while (i > -1) {
            result.push({
                before: string.slice(0, i),
                chr: string.charAt(i),
                after: string.slice(i + 1)
            });

            i = lower.indexOf(lchr, i + 1);
        }
        return result;
    }

    /**
     * Escapes a string so that it can be interpreted as HTML node content.
     *
     * WARNING: The output isn't safe for including in attributes, and I
     *          haven't considered other HTML contexts.
     * NOTE: This really is worth it compared to using $('<div>').text(foo).html().
     *
     * @param string, the string to escape
     * @return string, the escaped version.
     */
    function htmlEscape(string) {
        return string.replace(/&/g, '&')
                     .replace(/</g, '<')
                     .replace(/>/g, '>');
    }

    /**
     * Generates a case-insensitive match of the abbreviation against the string
     *
     * @param string, a canonical string to be matched against.
     * @param abbreviation, an abbreviation that a user may have typed
     *                      in order to specify that string.
     *
     * @cache (private), a cache that reduces the expected running time of the
     *                   algorithm in the case there are many repeated characters.
     *
     * @return {
     *    score:  A score (0 <= score <= 1) that indicates how likely it is that
     *            the abbreviation matches the string.
     *
     *            The score is 0 if the characters in the abbreviation do not
     *            all appear in order in the string.
     *
     *            The score is 1 if the user typed the exact string.
     *
     *            Scores are designed to be comparable when many different
     *            strings are matched against the same abbreviation, for example
     *            for autocompleting.
     *
     *    html:   A copy of the input string html-escaped, with matching letters
     *            surrounded by <b> and </b>.
     *
     * }
    **/
    $.fuzzyMatch = function (string, abbreviation, cache) {
        if (abbreviation === "") {
            return {
                score: string === "" ? SCORE_CONTINUE_MATCH : PENALTY_NOT_COMPLETE,
                html: htmlEscape(string)
            };
        }

        if (cache && cache[string] && cache[string][abbreviation]) {
            return $.extend({}, cache[string][abbreviation]);
        }

        cache = cache || {};
        cache[string] = cache[string] || {};
        cache[string][abbreviation] =

           $(allCaseInsensitiveSplits(string, abbreviation.charAt(0)))
            .map(function (i, split) {
                var result = $.fuzzyMatch(split.after, abbreviation.slice(1), cache),
                    preceding_char = split.before.charAt(split.before.length - 1);

                if (split.before === "") {
                    result.score *= SCORE_CONTINUE_MATCH;

                } else if (preceding_char.match(/[\\\/\-_+.# \t"@\[\(\{&]/) ||
                        (split.chr.toLowerCase() !== split.chr && preceding_char.toLowerCase() === preceding_char)) {

                    result.score *= SCORE_START_WORD;
                } else {
                    result.score *= SCORE_OK;
                }

                if (split.chr !== abbreviation.charAt(0)) {
                    result.score *= PENALTY_CASE_MISMATCH;
                }

                result.score *= Math.pow(PENALTY_SKIPPED, split.before.length);
                result.html = htmlEscape(split.before) + '<b>' + htmlEscape(split.chr) + '</b>'  + result.html;

                return result;
            })
            .sort(function (a, b) {
                return a.score < b.score ? 1 : a.score === b.score ? 0 : -1;
            })[0] ||

            // No matches for the next character in the abbreviation, abort!
            {
                score: 0, // This 0 will multiply up to the top, giving a total of 0
                html: htmlEscape(string)
            };

        return $.extend({}, cache[string][abbreviation]);
    };
/*global jQuery */
}(jQuery));
<|endoftext|>


<commit_before>/*
 * jQuery Microtools - (c) Matthias Lienau
 * License: [The MIT License](http://opensource.org/licenses/MIT)
 * github.com/atufkas
 * www.mlienau.de
 */
(function($) {

  function isOverflown(e,axis) {
    var ox = e.scrollWidth > e.clientWidth + 20;
    var oy = e.scrollHeight > e.clientHeight;

    switch(axis) {
      case 'x':
        return ox;
      case 'y':
        return oy;
      default:
        return ox||oy;
    }
  }

  var pseudos = {
		'mt-overflow': function(elm) {
			return isOverflown(elm);
		},

		'mt-overflow-x': function(elm) {
			return isOverflown(elm,'x');
		},

		'mt-overflow-y': function(elm) {
			return isOverflown(elm,'y');
		}
	};

	$.extend($.expr[':'], pseudos);

})(jQuery);<commit_msg>Fixed overflow-x return value.<commit_after>/*
 * jQuery Microtools - (c) Matthias Lienau
 * License: [The MIT License](http://opensource.org/licenses/MIT)
 * github.com/atufkas
 * www.mlienau.de
 */
(function($) {

  function isOverflown(e,axis) {
    var ox = e.scrollWidth > e.clientWidth;
    var oy = e.scrollHeight > e.clientHeight;

    switch(axis) {
      case 'x':
        return ox;
      case 'y':
        return oy;
      default:
        return ox||oy;
    }
  }

  var pseudos = {
		'mt-overflow': function(elm) {
			return isOverflown(elm);
		},

		'mt-overflow-x': function(elm) {
			return isOverflown(elm,'x');
		},

		'mt-overflow-y': function(elm) {
			return isOverflown(elm,'y');
		}
	};

	$.extend($.expr[':'], pseudos);

})(jQuery);<|endoftext|>


<commit_before>/**
 * AJAX Nette Framwork plugin for jQuery
 *
 * @copyright Copyright (c) 2009, 2010 Jan Marek
 * @copyright Copyright (c) 2009, 2010 David Grudl
 * @copyright Copyright (c) 2012 Vojtěch Dobeš
 * @license MIT
 */

(function($, undefined) {

if (typeof $ != 'function') return;

var nette = function () {
	var inner = {
		self: this,
		initialized: false,
		contexts: {},
		extensions: {},
		on: {
			init: {},
			load: {},
			before: {},
			start: {},
			success: {},
			complete: {},
			error: {}
		},
		fire: function () {
			var result = true;
			var args = Array.prototype.slice.call(arguments);
			var name = args.shift();
			args.push(inner.self);
			$.each(inner.on[name], function (index, reaction) {
				var temp = reaction.apply(inner.contexts[index], args);
				return result = (temp === undefined || temp);
			});
			return result;
		},
		requestHandler: function (e) {
			e.preventDefault();
			if (inner.fire('before', this)) {
				var $el, $form, isForm, isSubmit, data = {};

				$el = $(this);

				if ((isForm = $el.is('form')) || (isSubmit = $el.is(':submit'))) {
					if (isSubmit) {
						$form = $el.closest('form');
						data[$el.attr('name')] = $el.val() || '';
					} else if (isForm) {
						$form = $el;
					} else {
						return;
					}

					if ($form.get(0).onsubmit && !$form.get(0).onsubmit()) return null;

					var values = $form.serializeArray();
					for (var i = 0; i < values.length; i++) {
						var name = values[i].name;
						if (name in data) {
							var val = data[name];
							if (!(val instanceof Array)) {
								val = [val];
							}
							val.push(values[i].value);
							data[name] = val;
						} else {
							data[name] = values[i].value;
						}
					}
				}

			// thx to @vrana
			var url = $form ? $form.attr('action') : this.href;
			if (/:|^#/.test(url)) return;

				var req = $.ajax({
					url: url,
					data: data,
					type: $form ? $form.attr('method') : 'get',
					success: function (payload) {
						inner.fire('success', payload);
					},
					complete: function () {
						inner.fire('complete');
					},
					error: function () {
						inner.fire('error');
					}
				});
				inner.fire('start', req);
			}
		}
	};

	this.ext = function (name, callbacks, context) {
		if (inner.initialized) throw 'Cannot manipulate nette-ajax extensions after initialization.';

		if (callbacks === undefined) {
			return inner.extensions[name];
		} else if (!callbacks) {
			inner.extensions[name] = undefined;
			return this;
		} else if (inner.extensions[name]) {
			throw 'Cannot override already registered nette-ajax extension.';
		} else {
			return inner.extensions[name] = {
				name: name,
				on: callbacks,
				context: context
			};
		}
	};

	this.init = function (load, loadContext) {
		if (typeof load == 'function') {
			this.ext('init', null);
			this.ext('init', {
				load: load,
			}, loadContext);
		} else if (typeof load == 'object') {
			this.ext('init', null);
			this.ext('init', load, loadContext);
		} else if (load !== undefined) {
			throw 'Argument of init() can be function or function-hash only.';
		} else {
			if (inner.initialized) throw 'Cannot initialize nette-ajax twice.';
		}

		inner.initialized = true;

		$.each(inner.extensions, function (index, extension) {
			$.each(['init', 'load', 'before', 'start', 'success', 'complete', 'error'], function (index, reaction) {
				if (extension !== undefined) {
					if (extension.on[reaction] !== undefined) {
						inner.on[reaction][extension.name] = extension.on[reaction];
					}
					inner.contexts[extension.name] = extension.context ? extension.context : {};
				}
			});
		});

		inner.fire('init');
		this.load();
		return this;
	};

	this.load = function () {
		inner.fire('load', inner.requestHandler);
	};
};

$.nette = new ($.extend(nette, $.nette ? $.nette : {}));

// default snippet handler
$.nette.ext('snippets', {
	success: function (payload) {
		if (payload.snippets) {
			for (var i in payload.snippets) {
				this.updateSnippet(i, payload.snippets[i]);
			}
		}
	}
}, {
	updateSnippet: function (id, html) {
		var $el = $('#' + this.escapeSelector(id));
		// Fix for setting document title in IE
		if ($el.eq(0).tagName == 'TITLE') {
			document.title = html;
		} else {
			this.applySnippet($el, html);
		}
	},
	applySnippet: function ($el, html) {
		$el.html(html);
	},
	escapeSelector: function (selector) {
		// thx to @uestla (https://github.com/uestla)
		return selector.replace(/[\!"#\$%&'\(\)\*\+,\.\/:;<=>\?@\[\\\]\^`\{\|\}~]/g, '\\$&');
	}
});

// support $this->redirect()
$.nette.ext('redirect', {
	success: function (payload) {
		if (payload.redirect) {
			window.location.href = payload.redirect;
			return false;
		}
	}
});

// change URL (requires HTML5)
$.nette.ext('history', {
	before: function (ui) {
		var $el = $(ui);
		if ($el.is('a')) {
			this.href = ui.href;
		}
	},
	success: function (payload) {
		if (payload.url) {
			this.href = payload.url;
		}
		if (!payload.signal && window.history && history.pushState && this.href) {
			history.pushState({href: this.href}, '', this.href);
		}
	}
}, {href: null});

// current page state
$.nette.ext('state', {
	success: function (payload) {
		if (payload.state) {
			this.state = payload.state;
		}
	}
}, {state: null});

// abort last request if new started
$.nette.ext('unique', {
	start: function (req) {
		if (this.req) {
			this.req.abort();
		}
		this.req = req;
	},
	complete: function () {
		this.req = null;
	}
}, {req: null});

// option to abort by ESC (thx to @vrana)
$.nette.ext('abort', {
	init: function () {
		$('body').keydown($.proxy(function (e) {
			if (this.req && (e.keyCode == 27 // Esc
			&& !(e.ctrlKey || e.shiftKey || e.altKey || e.metaKey))
			) {
				this.req.abort();
			}
		}, this));
	},
	start: function (req) {
		this.req = req;
	},
	complete: function () {
		this.req = null;
	}
}, {req: null});

// current page state
$.nette.ext('init', {
	load: function (rh) {
		$(this.linkSelector).off('click', rh).on('click', rh);
		var $forms = $(this.formSelector);
		$forms.off('submit', rh).on('submit', rh);
		$forms.off('click', ':submit', rh).on('click', ':submit', rh);
	},
	success: function () {
		$.nette.load();
	}
}, {
	linkSelector: 'a.ajax',
	formSelector: 'form.ajax'
});

})(jQuery);
<commit_msg>Forms are sent the non-ajax away with CTRL etc.<commit_after>/**
 * AJAX Nette Framwork plugin for jQuery
 *
 * @copyright Copyright (c) 2009, 2010 Jan Marek
 * @copyright Copyright (c) 2009, 2010 David Grudl
 * @copyright Copyright (c) 2012 Vojtěch Dobeš
 * @license MIT
 */

(function($, undefined) {

if (typeof $ != 'function') return;

var nette = function () {
	var inner = {
		self: this,
		initialized: false,
		contexts: {},
		extensions: {},
		on: {
			init: {},
			load: {},
			before: {},
			start: {},
			success: {},
			complete: {},
			error: {}
		},
		fire: function () {
			var result = true;
			var args = Array.prototype.slice.call(arguments);
			var name = args.shift();
			args.push(inner.self);
			$.each(inner.on[name], function (index, reaction) {
				var temp = reaction.apply(inner.contexts[index], args);
				return result = (temp === undefined || temp);
			});
			return result;
		},
		explicitNoAjax: false,
		requestHandler: function (e) {
			e.stopPropagation();

			// thx to @vrana
			var explicitNoAjax = e.button || e.ctrlKey || e.shiftKey || e.altKey || e.metaKey;

			var $el = $(this), $form, isForm, isSubmit, data = {};
			if ((isForm = $el.is('form')) || (isSubmit = $el.is(':submit'))) {
				if (isSubmit) {
					$form = $el.closest('form');
					data[$el.attr('name')] = $el.val() || '';
				} else if (isForm) {
					$form = $el;
				} else {
					return;
				}

				if (explicitNoAjax && isSubmit) {
					inner.explicitNoAjax = true;
					return;
				} else if (isForm && inner.explicitNoAjax) {
					inner.explicitNoAjax = false;
					return;
				}

				if ($form.get(0).onsubmit && !$form.get(0).onsubmit()) return null;

				var values = $form.serializeArray();
				for (var i = 0; i < values.length; i++) {
					var name = values[i].name;
					if (name in data) {
						var val = data[name];
						if (!(val instanceof Array)) {
							val = [val];
						}
						val.push(values[i].value);
						data[name] = val;
					} else {
						data[name] = values[i].value;
					}
				}
			} else if (explicitNoAjax) return;

			// thx to @vrana
			var url = $form ? $form.attr('action') : this.href;
			if (/:|^#/.test(url)) return;

			if (inner.fire('before', this)) {
				e.preventDefault();
				var req = $.ajax({
					url: url,
					data: data,
					type: $form ? $form.attr('method') : 'get',
					success: function (payload) {
						inner.fire('success', payload);
					},
					complete: function () {
						inner.fire('complete');
					},
					error: function () {
						inner.fire('error');
					}
				});
				inner.fire('start', req);
			}
		}
	};

	this.ext = function (name, callbacks, context) {
		if (inner.initialized) throw 'Cannot manipulate nette-ajax extensions after initialization.';

		if (callbacks === undefined) {
			return inner.extensions[name];
		} else if (!callbacks) {
			inner.extensions[name] = undefined;
			return this;
		} else if (inner.extensions[name]) {
			throw 'Cannot override already registered nette-ajax extension.';
		} else {
			return inner.extensions[name] = {
				name: name,
				on: callbacks,
				context: context
			};
		}
	};

	this.init = function (load, loadContext) {
		if (typeof load == 'function') {
			this.ext('init', null);
			this.ext('init', {
				load: load,
			}, loadContext);
		} else if (typeof load == 'object') {
			this.ext('init', null);
			this.ext('init', load, loadContext);
		} else if (load !== undefined) {
			throw 'Argument of init() can be function or function-hash only.';
		} else {
			if (inner.initialized) throw 'Cannot initialize nette-ajax twice.';
		}

		inner.initialized = true;

		$.each(inner.extensions, function (index, extension) {
			$.each(['init', 'load', 'before', 'start', 'success', 'complete', 'error'], function (index, reaction) {
				if (extension !== undefined) {
					if (extension.on[reaction] !== undefined) {
						inner.on[reaction][extension.name] = extension.on[reaction];
					}
					inner.contexts[extension.name] = extension.context ? extension.context : {};
				}
			});
		});

		inner.fire('init');
		this.load();
		return this;
	};

	this.load = function () {
		inner.fire('load', inner.requestHandler);
	};
};

$.nette = new ($.extend(nette, $.nette ? $.nette : {}));

// default snippet handler
$.nette.ext('snippets', {
	success: function (payload) {
		if (payload.snippets) {
			for (var i in payload.snippets) {
				this.updateSnippet(i, payload.snippets[i]);
			}
		}
	}
}, {
	updateSnippet: function (id, html) {
		var $el = $('#' + this.escapeSelector(id));
		// Fix for setting document title in IE
		if ($el.eq(0).tagName == 'TITLE') {
			document.title = html;
		} else {
			this.applySnippet($el, html);
		}
	},
	applySnippet: function ($el, html) {
		$el.html(html);
	},
	escapeSelector: function (selector) {
		// thx to @uestla (https://github.com/uestla)
		return selector.replace(/[\!"#\$%&'\(\)\*\+,\.\/:;<=>\?@\[\\\]\^`\{\|\}~]/g, '\\$&');
	}
});

// support $this->redirect()
$.nette.ext('redirect', {
	success: function (payload) {
		if (payload.redirect) {
			window.location.href = payload.redirect;
			return false;
		}
	}
});

// change URL (requires HTML5)
$.nette.ext('history', {
	before: function (ui) {
		var $el = $(ui);
		if ($el.is('a')) {
			this.href = ui.href;
		}
	},
	success: function (payload) {
		if (payload.url) {
			this.href = payload.url;
		}
		if (!payload.signal && window.history && history.pushState && this.href) {
			history.pushState({href: this.href}, '', this.href);
		}
	}
}, {href: null});

// current page state
$.nette.ext('state', {
	success: function (payload) {
		if (payload.state) {
			this.state = payload.state;
		}
	}
}, {state: null});

// abort last request if new started
$.nette.ext('unique', {
	start: function (req) {
		if (this.req) {
			this.req.abort();
		}
		this.req = req;
	},
	complete: function () {
		this.req = null;
	}
}, {req: null});

// option to abort by ESC (thx to @vrana)
$.nette.ext('abort', {
	init: function () {
		$('body').keydown($.proxy(function (e) {
			if (this.req && (e.keyCode == 27 // Esc
			&& !(e.ctrlKey || e.shiftKey || e.altKey || e.metaKey))
			) {
				this.req.abort();
			}
		}, this));
	},
	start: function (req) {
		this.req = req;
	},
	complete: function () {
		this.req = null;
	}
}, {req: null});

// current page state
$.nette.ext('init', {
	load: function (rh) {
		$(this.linkSelector).off('click', rh).on('click', rh);
		var $forms = $(this.formSelector);
		$forms.off('submit', rh).on('submit', rh);
		$forms.off('click', ':submit', rh).on('click', ':submit', rh);
	},
	success: function () {
		$.nette.load();
	}
}, {
	linkSelector: 'a.ajax',
	formSelector: 'form.ajax'
});

})(jQuery);
<|endoftext|>