brancusi 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. data/Rakefile +13 -0
  2. data/lib/assets/javascripts/brancusi/application/application.js.coffee +114 -0
  3. data/lib/assets/javascripts/brancusi/application/application_controller.js.coffee +17 -0
  4. data/lib/assets/javascripts/brancusi/application/application_module.js.coffee +29 -0
  5. data/lib/assets/javascripts/brancusi/application/bootstrapper.js.coffee +18 -0
  6. data/lib/assets/javascripts/brancusi/application/index.js.coffee +1 -0
  7. data/lib/assets/javascripts/brancusi/application/sandbox.js.coffee +14 -0
  8. data/lib/assets/javascripts/brancusi/container/container.js.coffee +139 -0
  9. data/lib/assets/javascripts/brancusi/container/dependent_module.js.coffee +30 -0
  10. data/lib/assets/javascripts/brancusi/container/dependent_object.js.coffee +9 -0
  11. data/lib/assets/javascripts/brancusi/container/index.js.coffee +1 -0
  12. data/lib/assets/javascripts/brancusi/events/event_object.js.coffee +10 -0
  13. data/lib/assets/javascripts/brancusi/events/events_module.js.coffee +14 -0
  14. data/lib/assets/javascripts/brancusi/events/index.js.coffee +1 -0
  15. data/lib/assets/javascripts/brancusi/events/mediator.js.coffee +70 -0
  16. data/lib/assets/javascripts/brancusi/index.js.coffee +2 -0
  17. data/lib/assets/javascripts/brancusi/namespace.js.coffee +13 -0
  18. data/lib/assets/javascripts/brancusi/object_model/base_object.js.coffee +27 -0
  19. data/lib/assets/javascripts/brancusi/object_model/decorate.js.coffee +14 -0
  20. data/lib/assets/javascripts/brancusi/object_model/extend.js.coffee +10 -0
  21. data/lib/assets/javascripts/brancusi/object_model/include.js.coffee +13 -0
  22. data/lib/assets/javascripts/brancusi/object_model/index.js.coffee +1 -0
  23. data/lib/assets/javascripts/brancusi/renderer/index.js.coffee +1 -0
  24. data/lib/assets/javascripts/brancusi/renderer/region_manager.js.coffee +30 -0
  25. data/lib/assets/javascripts/brancusi/renderer/renderer.js.coffee +41 -0
  26. data/lib/assets/javascripts/brancusi/renderer/template_manager.js.coffee +0 -0
  27. data/lib/assets/javascripts/brancusi/routes/dispatcher.js.coffee +10 -0
  28. data/lib/assets/javascripts/brancusi/routes/index.js.coffee +1 -0
  29. data/lib/assets/javascripts/brancusi/routes/mapper.js.coffee +41 -0
  30. data/lib/assets/javascripts/brancusi/routes/router.js.coffee +15 -0
  31. data/lib/assets/javascripts/brancusi/support/davis_router.js.coffee +19 -0
  32. data/lib/assets/javascripts/brancusi/support/knockout_renderer.js.coffee +12 -0
  33. data/lib/assets/javascripts/davis.js +1838 -0
  34. data/lib/assets/javascripts/knockout.js +3583 -0
  35. data/lib/assets/javascripts/underscore.js +1221 -0
  36. data/lib/assets/javascripts/underscore.string.js +600 -0
  37. data/lib/brancusi.rb +4 -0
  38. data/lib/brancusi/engine.rb +4 -0
  39. data/lib/brancusi/version.rb +3 -0
  40. metadata +282 -0
@@ -0,0 +1,2 @@
1
+ #= require ./namespace
2
+ #= require_tree .
@@ -0,0 +1,13 @@
1
+ window.namespace = (scope) ->
2
+
3
+ add_namespace = (scope, ctx) ->
4
+ [outer, rest...] = scope
5
+
6
+ if not ctx[outer]?
7
+ ctx[outer] = {}
8
+
9
+ if rest.length
10
+ add_namespace rest, ctx[outer]
11
+
12
+ add_namespace scope.split("."), window
13
+
@@ -0,0 +1,27 @@
1
+ #= require ./include
2
+ #= require ./decorate
3
+
4
+ namespace "brancusi"
5
+
6
+ # Implements convenience methods to include mixins and decorate instances.
7
+ #
8
+ class brancusi.BaseObject
9
+
10
+ # Static method to include a module into the class.
11
+ #
12
+ # @param module [Object] the module to include.
13
+ #
14
+ @include: (module) ->
15
+ brancusi.include(@, module)
16
+
17
+ # Static method to extend the class with a module.
18
+ #
19
+ # @param module [Object] the module to include.
20
+ #
21
+ @extend: (module) ->
22
+ brancusi.extend(@, module)
23
+
24
+ # Decorates the instance with the given decorator.
25
+ #
26
+ decorate: (decorator, args...)->
27
+ brancusi.decorate(@, decorator, args...)
@@ -0,0 +1,14 @@
1
+ #= require ./extend
2
+
3
+ namespace "brancusi"
4
+
5
+ # Decorates the target object with methods from the given decorator, and invokes the constructor.
6
+ #
7
+ # @param target [Object] the object to decorate.
8
+ # @param decorator [Class] the decorator to use.
9
+ # @param args... [Array] the arguments to pass to the decorator's constructor.
10
+ #
11
+ brancusi.decorate = ( target, decorator, args... ) ->
12
+
13
+ brancusi.extend( target, decorator )
14
+ decorator.apply( target, args )
@@ -0,0 +1,10 @@
1
+ namespace "brancusi"
2
+
3
+ # Extends the target with methods from the given module's prototype.
4
+ #
5
+ # @param target [Object/Class] the object or class to extend.
6
+ # @param module [Class] the module to mixin.
7
+ #
8
+ brancusi.extend = ( target, module ) ->
9
+ for key, value of module::
10
+ target[key] = value
@@ -0,0 +1,13 @@
1
+ namespace "brancusi"
2
+
3
+ # Extends the target with methods from the given mixin.
4
+ #
5
+ # @param target [Class] the class to mix in the module.
6
+ # @param module [Class] the module to mixin.
7
+ #
8
+ brancusi.include = ( target, module ) ->
9
+ for key, value of module
10
+ target[key] = value
11
+
12
+ for key, value of module::
13
+ target::[key] = value
@@ -0,0 +1 @@
1
+ #= require_tree .
@@ -0,0 +1 @@
1
+ #= require_tree .
@@ -0,0 +1,30 @@
1
+ namespace "brancusi.renderer"
2
+
3
+ class brancusi.renderer.RegionManager
4
+
5
+ # Looks up and returns a region by name.
6
+ #
7
+ # @param region_name [String] the name of the region.
8
+ # @return [Array] the jQuery array of matching elements.
9
+ #
10
+ find: (region_name) ->
11
+ $("[data-region='#{region_name}']")
12
+
13
+
14
+ # Returns the child regions in the given document element.
15
+ #
16
+ # @param el [Element] the element to search within.
17
+ # @return [Array] a jQuery array of matching elements.
18
+ #
19
+ children: (el) ->
20
+ $(el).find("[data-region]")
21
+
22
+
23
+ # Returns the name of the given region element.
24
+ #
25
+ # @param region [Element] the region.
26
+ # @return [String] the name of the region.
27
+ #
28
+ region_name: (region) ->
29
+ $(region).data("region")
30
+
@@ -0,0 +1,41 @@
1
+ namespace "brancusi.renderer"
2
+
3
+ class brancusi.renderer.Renderer extends brancusi.ApplicationModule
4
+
5
+ @dependency region_manager: 'RegionManager'
6
+
7
+ constructor: ->
8
+ super('renderer')
9
+ @default_bindings = {}
10
+
11
+ # Renders a region with the given data/template bindings, then recursively renders any child regions.
12
+ #
13
+ # @param region [Element] the document region to render.
14
+ # @param bindings [Object] an object specifying the binding mappings.
15
+ #
16
+ render_region: (region, bindings) ->
17
+ region_name = @region_manager.region_name(region)
18
+ binding = bindings?[region_name]
19
+
20
+ template = binding?.template || region_name
21
+ data = binding?.data
22
+
23
+ @render_template(template, data, region)
24
+
25
+ for child in @region_manager.children(region)
26
+ @render_region($(child), bindings)
27
+
28
+
29
+ # Renders a page with the given template and data model.
30
+ #
31
+ # @param template [String] the name of the template to render the 'content' region.
32
+ # @param data [Object] the data model for the 'content' region.
33
+ # @param bindings [Object] any further data bindings (e.g. for other regions).
34
+ #
35
+ render_page: (template, data, bindings = {}) ->
36
+ bindings = _.defaults bindings, @default_bindings,
37
+ content: { template: template, data: data }
38
+
39
+ master_region = @region_manager.find('master')
40
+ @render_region(master_region, bindings)
41
+
@@ -0,0 +1,10 @@
1
+ namespace "brancusi.routes"
2
+
3
+ class brancusi.routes.Dispatcher extends brancusi.DependentObject
4
+
5
+ constructor: (@app, @controller_name, @action_name) ->
6
+
7
+ dispatch: =>
8
+ @controller = @app.controllers["#{@controller_name}"] unless @controller?
9
+ @controller.begin_request(@action_name)
10
+ @controller[@action_name].apply(@controller, arguments)
@@ -0,0 +1 @@
1
+ #= require_tree .
@@ -0,0 +1,41 @@
1
+ namespace "brancusi.routes"
2
+
3
+ # given a url, maps to a controller + action
4
+ # given a resource, maps to a url
5
+
6
+ class brancusi.routes.Mapper
7
+
8
+ constructor: ->
9
+ @mappings = {}
10
+
11
+ draw: (block) ->
12
+ block.apply(@)
13
+
14
+ match: (url, opts) ->
15
+ @mappings[url] = @parse(opts)
16
+
17
+ parse: (opts) ->
18
+ if _.isString(opts)
19
+ [controller, action] = /(.*)#(.*)/.exec(opts)[1..2]
20
+ { controller: controller, action: action }
21
+ else
22
+ opts
23
+
24
+ apply: (app, router) ->
25
+ for url, { controller, action } of @mappings
26
+ dispatcher = new brancusi.routes.Dispatcher(app, controller, action)
27
+ router.route(url, dispatcher.dispatch)
28
+
29
+ # resource: ( resource ) ->
30
+ # for action in ['view', 'edit']
31
+ # path_name = opt?.paths_names?[action]?
32
+ # path_name ?= action
33
+ # @router.route "#{resource}/:id/#{action}", resource, action
34
+ # for action in ['create']
35
+ # path_name = opt?.paths_names?[action]?
36
+ # path_name ?= action
37
+ # @router.route "#{resource}/#{action}", resource, action
38
+
39
+ #namespace: ( path, block ) ->
40
+ # @scope.push path
41
+ # @draw( block )
@@ -0,0 +1,15 @@
1
+ class brancusi.Router extends brancusi.ApplicationModule
2
+
3
+ constructor: ->
4
+ super('router')
5
+
6
+ initialize: (app) ->
7
+ console.log('initializing router')
8
+ @configure(app.constructor.config)
9
+ app.constructor.routes.apply(app, @)
10
+
11
+ route: (url, action) ->
12
+ throw new Error("Missing implementation for Router.route")
13
+
14
+ configure: (config) ->
15
+ throw new Error("Missing implementation for Router.configure")
@@ -0,0 +1,19 @@
1
+ namespace "brancusi.routes"
2
+
3
+ class brancusi.routes.DavisRouter extends brancusi.Router
4
+
5
+ constructor: ->
6
+ @davis = new Davis
7
+
8
+ route: (url, action) ->
9
+ @davis.get(url, action)
10
+
11
+ configure: (config) ->
12
+ @davis.configure(-> @generateRequestOnPageLoad = true) if config.route_on_load
13
+
14
+ @on "application.initialize", (app) ->
15
+ @initialize(app)
16
+
17
+ @on "application.ready", ->
18
+ @davis.start()
19
+
@@ -0,0 +1,12 @@
1
+ namespace "brancusi.renderer"
2
+
3
+ class brancusi.renderer.KnockoutRenderer extends brancusi.renderer.Renderer
4
+
5
+ render_template: (template, data, target) ->
6
+ target.html $("<div></div>").attr 'data-bind',
7
+ "template: 'template:#{template}'"
8
+
9
+ ko.applyBindings(data || {}, target[0])
10
+
11
+
12
+
@@ -0,0 +1,1838 @@
1
+ /*!
2
+ * Davis - http://davisjs.com - JavaScript Routing - 0.9.6
3
+ * Copyright (C) 2011 Oliver Nightingale
4
+ * MIT Licensed
5
+ */
6
+ ;
7
+ /**
8
+ * Convinience method for instantiating a new Davis app and configuring it to use the passed
9
+ * routes and subscriptions.
10
+ *
11
+ * @param {Function} config A function that will be run with a newly created Davis.App as its context,
12
+ * should be used to set up app routes, subscriptions and settings etc.
13
+ * @namespace
14
+ * @returns {Davis.App}
15
+ */
16
+ Davis = function (config) {
17
+ var app = new Davis.App
18
+ config && config.call(app)
19
+ Davis.$(function () { app.start() })
20
+ return app
21
+ };
22
+
23
+ /**
24
+ * Stores the DOM library that Davis will use. Can be overriden to use libraries other than jQuery.
25
+ */
26
+ if (window.jQuery) {
27
+ Davis.$ = jQuery
28
+ } else {
29
+ Davis.$ = null
30
+ };
31
+
32
+ /**
33
+ * Checks whether Davis is supported in the current browser
34
+ *
35
+ * @returns {Boolean}
36
+ */
37
+ Davis.supported = function () {
38
+ return (typeof window.history.pushState == 'function')
39
+ }
40
+
41
+ /*!
42
+ * A function that does nothing, used as a default param for any callbacks.
43
+ *
44
+ * @private
45
+ * @returns {Function}
46
+ */
47
+ Davis.noop = function () {}
48
+
49
+ /**
50
+ * Method to extend the Davis library with an extension.
51
+ *
52
+ * An extension is just a function that will modify the Davis framework in some way,
53
+ * for example changing how the routing works or adjusting where Davis thinks it is supported.
54
+ *
55
+ * Example:
56
+ * Davis.extend(Davis.hashBasedRouting)
57
+ *
58
+ * @param {Function} extension the function that will extend Davis
59
+ *
60
+ */
61
+ Davis.extend = function (extension) {
62
+ extension(Davis)
63
+ }
64
+
65
+ /*!
66
+ * the version
67
+ */
68
+ Davis.version = "0.9.6";/*!
69
+ * Davis - utils
70
+ * Copyright (C) 2011 Oliver Nightingale
71
+ * MIT Licensed
72
+ */
73
+
74
+ /*!
75
+ * A module that provides wrappers around modern JavaScript so that native implementations are used
76
+ * whereever possible and JavaScript implementations are used in those browsers that do not natively
77
+ * support them.
78
+ */
79
+ Davis.utils = (function () {
80
+
81
+ /*!
82
+ * A wrapper around native Array.prototype.every.
83
+ *
84
+ * Falls back to a pure JavaScript implementation in browsers that do not support Array.prototype.every.
85
+ * For more details see the full docs on MDC https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/every
86
+ *
87
+ * @private
88
+ * @param {array} the array to loop through
89
+ * @param {fn} the function to that performs the every check
90
+ * @param {thisp} an optional param that will be set as fn's this value
91
+ * @returns {Array}
92
+ */
93
+ if (Array.prototype.every) {
94
+ var every = function (array, fn) {
95
+ return array.every(fn, arguments[2])
96
+ }
97
+ } else {
98
+ var every = function (array, fn) {
99
+ if (array === void 0 || array === null) throw new TypeError();
100
+ var t = Object(array);
101
+ var len = t.length >>> 0;
102
+ if (typeof fn !== "function") throw new TypeError();
103
+
104
+ var thisp = arguments[2];
105
+ for (var i = 0; i < len; i++) {
106
+ if (i in t && !fn.call(thisp, t[i], i, t)) return false;
107
+ }
108
+
109
+ return true;
110
+ }
111
+ };
112
+
113
+ /*!
114
+ * A wrapper around native Array.prototype.forEach.
115
+ *
116
+ * Falls back to a pure JavaScript implementation in browsers that do not support Array.prototype.forEach.
117
+ * For more details see the full docs on MDC https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/forEach
118
+ *
119
+ * @private
120
+ * @param {array} the array to loop through
121
+ * @param {fn} the function to apply to every element of the array
122
+ * @param {thisp} an optional param that will be set as fn's this value
123
+ * @returns {Array}
124
+ */
125
+ if (Array.prototype.forEach) {
126
+ var forEach = function (array, fn) {
127
+ return array.forEach(fn, arguments[2])
128
+ }
129
+ } else {
130
+ var forEach = function (array, fn) {
131
+ if (array === void 0 || array === null) throw new TypeError();
132
+ var t = Object(array);
133
+ var len = t.length >>> 0;
134
+ if (typeof fn !== "function") throw new TypeError();
135
+
136
+
137
+ var thisp = arguments[2];
138
+ for (var i = 0; i < len; i++) {
139
+ if (i in t) fn.call(thisp, t[i], i, t);
140
+ }
141
+ };
142
+ };
143
+
144
+ /*!
145
+ * A wrapper around native Array.prototype.filter.
146
+ * Falls back to a pure JavaScript implementation in browsers that do not support Array.prototype.filter.
147
+ * For more details see the full docs on MDC https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/filter
148
+ *
149
+ * @private
150
+ * @param {array} the array to filter
151
+ * @param {fn} the function to do the filtering
152
+ * @param {thisp} an optional param that will be set as fn's this value
153
+ * @returns {Array}
154
+ */
155
+ if (Array.prototype.filter) {
156
+ var filter = function (array, fn) {
157
+ return array.filter(fn, arguments[2])
158
+ }
159
+ } else {
160
+ var filter = function(array, fn) {
161
+ if (array === void 0 || array === null) throw new TypeError();
162
+ var t = Object(array);
163
+ var len = t.length >>> 0;
164
+ if (typeof fn !== "function") throw new TypeError();
165
+
166
+
167
+ var res = [];
168
+ var thisp = arguments[2];
169
+ for (var i = 0; i < len; i++) {
170
+ if (i in t) {
171
+ var val = t[i]; // in case fn mutates this
172
+ if (fn.call(thisp, val, i, t)) res.push(val);
173
+ }
174
+ }
175
+
176
+ return res;
177
+ };
178
+ };
179
+
180
+ /*!
181
+ * A wrapper around native Array.prototype.map.
182
+ * Falls back to a pure JavaScript implementation in browsers that do not support Array.prototype.map.
183
+ * For more details see the full docs on MDC https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/map
184
+ *
185
+ * @private
186
+ * @param {array} the array to map
187
+ * @param {fn} the function to do the mapping
188
+ * @param {thisp} an optional param that will be set as fn's this value
189
+ * @returns {Array}
190
+ */
191
+
192
+ if (Array.prototype.map) {
193
+ var map = function (array, fn) {
194
+ return array.map(fn, arguments[2])
195
+ }
196
+ } else {
197
+ var map = function(array, fn) {
198
+ if (array === void 0 || array === null)
199
+ throw new TypeError();
200
+
201
+ var t = Object(array);
202
+ var len = t.length >>> 0;
203
+ if (typeof fn !== "function")
204
+ throw new TypeError();
205
+
206
+ var res = new Array(len);
207
+ var thisp = arguments[2];
208
+ for (var i = 0; i < len; i++) {
209
+ if (i in t)
210
+ res[i] = fn.call(thisp, t[i], i, t);
211
+ }
212
+
213
+ return res;
214
+ };
215
+ };
216
+
217
+ /*!
218
+ * A convinience function for converting arguments to a proper array
219
+ *
220
+ * @private
221
+ * @param {args} a functions arguments
222
+ * @param {start} an integer at which to start converting the arguments to an array
223
+ * @returns {Array}
224
+ */
225
+ var toArray = function (args, start) {
226
+ return Array.prototype.slice.call(args, start || 0)
227
+ }
228
+
229
+ /*!
230
+ * Exposing the public interface to the Utils module
231
+ * @private
232
+ */
233
+ return {
234
+ every: every,
235
+ forEach: forEach,
236
+ filter: filter,
237
+ toArray: toArray,
238
+ map: map
239
+ }
240
+ })()
241
+
242
+ /*!
243
+ * Davis - listener
244
+ * Copyright (C) 2011 Oliver Nightingale
245
+ * MIT Licensed
246
+ */
247
+
248
+ /**
249
+ * A module to bind to link clicks and form submits and turn what would normally be http requests
250
+ * into instances of Davis.Request. These request objects are then pushed onto the history stack
251
+ * using the Davis.history module.
252
+ *
253
+ * This module uses Davis.$, which by defualt is jQuery for its event binding and event object normalization.
254
+ * To use Davis with any, or no, JavaScript framework be sure to provide support for all the methods called
255
+ * on Davis.$.
256
+ *
257
+ * @module
258
+ */
259
+ Davis.listener = function () {
260
+
261
+ /*!
262
+ * Methods to check whether an element has an href or action that is local to this page
263
+ * @private
264
+ */
265
+ var originChecks = {
266
+ A: function (elem) {
267
+ return elem.host !== window.location.host || elem.protocol !== window.location.protocol
268
+ },
269
+
270
+ FORM: function (elem) {
271
+ var a = document.createElement('a')
272
+ a.href = elem.action
273
+ return this.A(a)
274
+ }
275
+ }
276
+
277
+ /*!
278
+ * Checks whether the target of a click or submit event has an href or action that is local to the
279
+ * current page. Only links or targets with local hrefs or actions will be handled by davis, all
280
+ * others will be ignored.
281
+ * @private
282
+ */
283
+ var differentOrigin = function (elem) {
284
+ if (!originChecks[elem.nodeName.toUpperCase()]) return true // the elem is neither a link or a form
285
+ return originChecks[elem.nodeName.toUpperCase()](elem)
286
+ }
287
+
288
+ var hasModifier = function (event) {
289
+ return (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey)
290
+ }
291
+
292
+ /*!
293
+ * A handler that creates a new Davis.Request and pushes it onto the history stack using Davis.history.
294
+ *
295
+ * @param {Function} targetExtractor a function that will be called with the event target jQuery object and should return an object with path, title and method.
296
+ * @private
297
+ */
298
+ var handler = function (targetExtractor) {
299
+ return function (event) {
300
+ if (hasModifier(event)) return true
301
+ if (differentOrigin(this)) return true
302
+
303
+ var request = new Davis.Request (targetExtractor.call(Davis.$(this)));
304
+ Davis.location.assign(request)
305
+ event.stopPropagation()
306
+ event.preventDefault()
307
+ return false;
308
+ };
309
+ };
310
+
311
+ /*!
312
+ * A handler specialized for click events. Gets the request details from a link elem
313
+ * @private
314
+ */
315
+ var clickHandler = handler(function () {
316
+ var self = this
317
+
318
+ return {
319
+ method: 'get',
320
+ fullPath: this.prop('href'),
321
+ title: this.attr('title'),
322
+ delegateToServer: function () {
323
+ window.location = self.prop('href')
324
+ }
325
+ };
326
+ });
327
+
328
+ /*!
329
+ * Decodes the url, including + characters.
330
+ * @private
331
+ */
332
+ var decodeUrl = function (str) {
333
+ return decodeURIComponent(str.replace(/\+/g, '%20'))
334
+ };
335
+
336
+ /*!
337
+ * A handler specialized for submit events. Gets the request details from a form elem
338
+ * @private
339
+ */
340
+ var submitHandler = handler(function () {
341
+ var self = this
342
+ return {
343
+ method: this.attr('method'),
344
+ fullPath: decodeUrl(this.serialize() ? [this.prop('action'), this.serialize()].join("?") : this.prop('action')),
345
+ title: this.attr('title'),
346
+ delegateToServer: function () {
347
+ self.submit()
348
+ }
349
+ };
350
+ });
351
+
352
+ /**
353
+ * Binds to both link clicks and form submits using jQuery's deleagate.
354
+ *
355
+ * Will catch all current and future links and forms. Uses the apps settings for the selector to use for links and forms
356
+ *
357
+ * @see Davis.App.settings
358
+ * @memberOf listener
359
+ */
360
+ this.listen = function () {
361
+ Davis.$(document).delegate(this.settings.formSelector, 'submit', submitHandler)
362
+ Davis.$(document).delegate(this.settings.linkSelector, 'click', clickHandler)
363
+ }
364
+
365
+ /**
366
+ * Unbinds all click and submit handlers that were attatched with listen.
367
+ *
368
+ * Will efectivley stop the current app from processing any requests and all links and forms will have their default
369
+ * behaviour restored.
370
+ *
371
+ * @see Davis.App.settings
372
+ * @memberOf listener
373
+ */
374
+ this.unlisten = function () {
375
+ Davis.$(document).undelegate(this.settings.linkSelector, 'click', clickHandler)
376
+ Davis.$(document).undelegate(this.settings.formSelector, 'submit', submitHandler)
377
+ }
378
+ }
379
+ /*!
380
+ * Davis - event
381
+ * Copyright (C) 2011 Oliver Nightingale
382
+ * MIT Licensed
383
+ */
384
+
385
+ /**
386
+ * A plugin that adds basic event capabilities to a Davis app, it is included by default.
387
+ *
388
+ * @module
389
+ */
390
+ Davis.event = function () {
391
+
392
+ /*!
393
+ * callback storage
394
+ */
395
+ var callbacks = {}
396
+
397
+ /**
398
+ * Binds a callback to a named event.
399
+ *
400
+ * The following events are triggered internally by Davis and can be bound to
401
+ *
402
+ * * start : Triggered when the application is started
403
+ * * lookupRoute : Triggered before looking up a route. The request being looked up is passed as an argument
404
+ * * runRoute : Triggered before running a route. The request and route being run are passed as arguments
405
+ * * routeNotFound : Triggered if no route for the current request can be found. The current request is passed as an arugment
406
+ * * requestHalted : Triggered when a before filter halts the current request. The current request is passed as an argument
407
+ * * unsupported : Triggered when starting a Davis app in a browser that doesn't support html5 pushState
408
+ *
409
+ * Example
410
+ *
411
+ * app.bind('runRoute', function () {
412
+ * console.log('about to run a route')
413
+ * })
414
+ *
415
+ * @param {String} event event name
416
+ * @param {Function} fn callback
417
+ * @memberOf event
418
+ */
419
+ this.bind = function (event, fn) {
420
+ (callbacks[event] = callbacks[event] || []).push(fn);
421
+ return this;
422
+ };
423
+
424
+ /**
425
+ * Triggers an event with the given arguments.
426
+ *
427
+ * @param {String} event event name
428
+ * @param {Mixed} ...
429
+ * @memberOf event
430
+ */
431
+ this.trigger = function (event) {
432
+ var args = Davis.utils.toArray(arguments, 1),
433
+ handlers = callbacks[event];
434
+
435
+ if (!handlers) return this
436
+
437
+ for (var i = 0, len = handlers.length; i < len; ++i) {
438
+ handlers[i].apply(this, args)
439
+ }
440
+
441
+ return this;
442
+ };
443
+ }
444
+ /*!
445
+ * Davis - logger
446
+ * Copyright (C) 2011 Oliver Nightingale
447
+ * MIT Licensed
448
+ */
449
+
450
+ /**
451
+ * A plugin for enhancing the standard logging available through the console object.
452
+ * Automatically included in all Davis apps.
453
+ *
454
+ * Generates log messages of varying severity in the format
455
+ *
456
+ * `[Sun Jan 23 2011 16:15:21 GMT+0000 (GMT)] <message>`
457
+ *
458
+ * @module
459
+ */
460
+ Davis.logger = function () {
461
+
462
+ /*!
463
+ * Generating the timestamp portion of the log message
464
+ * @private
465
+ */
466
+ function timestamp(){
467
+ return "[" + Date() + "]";
468
+ }
469
+
470
+ /*!
471
+ * Pushing the timestamp onto the front of the arguments to log
472
+ * @private
473
+ */
474
+ function prepArgs(args) {
475
+ var a = Davis.utils.toArray(args)
476
+ a.unshift(timestamp())
477
+ return a.join(' ');
478
+ }
479
+
480
+ var logType = function (logLevel) {
481
+ return function () {
482
+ if (window.console) console[logLevel](prepArgs(arguments));
483
+ }
484
+ }
485
+
486
+
487
+ /**
488
+ * Prints an error message to the console if the console is available.
489
+ *
490
+ * @params {String} All arguments are combined and logged to the console.
491
+ * @memberOf logger
492
+ */
493
+ var error = logType('error')
494
+
495
+ /**
496
+ * Prints an info message to the console if the console is available.
497
+ *
498
+ * @params {String} All arguments are combined and logged to the console.
499
+ * @memberOf logger
500
+ */
501
+ var info = logType('info')
502
+
503
+ /**
504
+ * Prints a warning message to the console if the console is available.
505
+ *
506
+ * @params {String} All arguments are combined and logged to the console.
507
+ * @memberOf logger
508
+ */
509
+ var warn = logType('warn')
510
+
511
+ /*!
512
+ * Exposes the public methods of the module
513
+ * @private
514
+ */
515
+ this.logger = {
516
+ error: error,
517
+ info: info,
518
+ warn: warn
519
+ }
520
+ }/*!
521
+ * Davis - Route
522
+ * Copyright (C) 2011 Oliver Nightingale
523
+ * MIT Licensed
524
+ */
525
+
526
+ Davis.Route = (function () {
527
+
528
+ var pathNameRegex = /:([\w\d]+)/g;
529
+ var pathNameReplacement = "([^\/]+)";
530
+
531
+ var splatNameRegex = /\*([\w\d]+)/g;
532
+ var splatNameReplacement = "(.*)";
533
+
534
+ var nameRegex = /[:|\*]([\w\d]+)/g
535
+
536
+ /**
537
+ * Davis.Routes are the main part of a Davis application. They consist of an HTTP method, a path
538
+ * and a callback function. When a link or a form that Davis has bound to are clicked or submitted
539
+ * a request is pushed on the history stack and a route that matches the path and method of the
540
+ * generated request is run.
541
+ *
542
+ * The path for the route can consist of placeholders for attributes, these will then be available
543
+ * on the request. Simple variables should be prefixed with a colan, and for splat style params use
544
+ * an asterisk.
545
+ *
546
+ * Inside the callback function 'this' is bound to the request.
547
+ *
548
+ * Example:
549
+ *
550
+ * var route = new Davis.Route ('get', '/foo/:id', function (req) {
551
+ * var id = req.params['id']
552
+ * // do something interesting!
553
+ * })
554
+ *
555
+ * var route = new Davis.Route ('get', '/foo/*splat', function (req) {
556
+ * var id = req.params['splat']
557
+ * // splat will contain everything after the /foo/ in the path.
558
+ * })
559
+ *
560
+ * You can include any number of route level 'middleware' when defining routes. These middlewares are
561
+ * run in order and need to explicitly call the next handler in the stack. Using route middleware allows
562
+ * you to share common logic between routes and is also a good place to load any data or do any async calls
563
+ * keeping your main handler simple and focused.
564
+ *
565
+ * Example:
566
+ *
567
+ * var loadUser = function (req, next) {
568
+ * $.get('/users/current', function (user) {
569
+ * req.user = user
570
+ * next(req)
571
+ * })
572
+ * }
573
+ *
574
+ * var route = new Davis.Route ('get', '/foo/:id', loadUser, function (req) {
575
+ * renderUser(req.user)
576
+ * })
577
+ *
578
+ * @constructor
579
+ * @param {String} method This should be one of either 'get', 'post', 'put', 'delete', 'before', 'after' or 'state'
580
+ * @param {String} path This string can contain place holders for variables, e.g. '/user/:id' or '/user/*splat'
581
+ * @param {Function} callback One or more callbacks that will be called in order when a request matching both the path and method is triggered.
582
+ */
583
+ var Route = function (method, path, handlers) {
584
+ var convertPathToRegExp = function () {
585
+ if (!(path instanceof RegExp)) {
586
+ var str = path
587
+ .replace(pathNameRegex, pathNameReplacement)
588
+ .replace(splatNameRegex, splatNameReplacement);
589
+
590
+ // Most browsers will reset this to zero after a replace call. IE will
591
+ // set it to the index of the last matched character.
592
+ path.lastIndex = 0;
593
+
594
+ return new RegExp("^" + str + "$", "gi");
595
+ } else {
596
+ return path;
597
+ };
598
+ };
599
+
600
+ var convertMethodToRegExp = function () {
601
+ if (!(method instanceof RegExp)) {
602
+ return new RegExp("^" + method + "$", "i");
603
+ } else {
604
+ return method
605
+ };
606
+ }
607
+
608
+ var capturePathParamNames = function () {
609
+ var names = [], a;
610
+ while ((a = nameRegex.exec(path))) names.push(a[1]);
611
+ return names;
612
+ };
613
+
614
+ this.paramNames = capturePathParamNames();
615
+ this.path = convertPathToRegExp();
616
+ this.method = convertMethodToRegExp();
617
+
618
+ if (typeof handlers === 'function') {
619
+ this.handlers = [handlers]
620
+ } else {
621
+ this.handlers = handlers;
622
+ }
623
+ }
624
+
625
+ /**
626
+ * Tests whether or not a route matches a particular request.
627
+ *
628
+ * Example:
629
+ *
630
+ * route.match('get', '/foo/12')
631
+ *
632
+ * @param {String} method the method to match against
633
+ * @param {String} path the path to match against
634
+ * @returns {Boolean}
635
+ */
636
+ Route.prototype.match = function (method, path) {
637
+ this.reset();
638
+ return (this.method.test(method)) && (this.path.test(path))
639
+ }
640
+
641
+ /**
642
+ * Resets the RegExps for method and path
643
+ */
644
+ Route.prototype.reset = function () {
645
+ this.method.lastIndex = 0;
646
+ this.path.lastIndex = 0;
647
+ }
648
+
649
+ /**
650
+ * Runs the callback associated with a particular route against the passed request.
651
+ *
652
+ * Any named params in the request path are extracted, as per the routes path, and
653
+ * added onto the requests params object.
654
+ *
655
+ * Example:
656
+ *
657
+ * route.run(request)
658
+ *
659
+ * @params {Davis.Request} request
660
+ * @returns {Object} whatever the routes callback returns
661
+ */
662
+ Route.prototype.run = function (request) {
663
+ this.reset();
664
+ var matches = this.path.exec(request.path);
665
+ if (matches) {
666
+ matches.shift();
667
+ for (var i=0; i < matches.length; i++) {
668
+ request.params[this.paramNames[i]] = matches[i];
669
+ };
670
+ };
671
+
672
+ var handlers = Davis.utils.map(this.handlers, function (handler, i) {
673
+ return function (req) {
674
+ return handler.call(req, req, handlers[i+1])
675
+ }
676
+ })
677
+
678
+ return handlers[0](request)
679
+ }
680
+
681
+ /**
682
+ * Converts the route to a string representation of itself by combining the method and path
683
+ * attributes.
684
+ *
685
+ * @returns {String} string representation of the route
686
+ */
687
+ Route.prototype.toString = function () {
688
+ return [this.method, this.path].join(' ');
689
+ }
690
+
691
+ /*!
692
+ * exposing the constructor
693
+ * @private
694
+ */
695
+ return Route;
696
+ })()
697
+ /*!
698
+ * Davis - router
699
+ * Copyright (C) 2011 Oliver Nightingale
700
+ * MIT Licensed
701
+ */
702
+
703
+ /**
704
+ * A decorator that adds convinience methods to a Davis.App for easily creating instances
705
+ * of Davis.Route and looking up routes for a particular request.
706
+ *
707
+ * Provides get, post put and delete method shortcuts for creating instances of Davis.Routes
708
+ * with the corresponding method. This allows simple REST styled routing for a client side
709
+ * JavaScript application.
710
+ *
711
+ * ### Example
712
+ *
713
+ * app.get('/foo/:id', function (req) {
714
+ * // get the foo with id = req.params['id']
715
+ * })
716
+ *
717
+ * app.post('/foo', function (req) {
718
+ * // create a new instance of foo with req.params
719
+ * })
720
+ *
721
+ * app.put('/foo/:id', function (req) {
722
+ * // update the instance of foo with id = req.params['id']
723
+ * })
724
+ *
725
+ * app.del('/foo/:id', function (req) {
726
+ * // delete the instance of foo with id = req.params['id']
727
+ * })
728
+ *
729
+ * As well as providing convinience methods for creating instances of Davis.Routes the router
730
+ * also provides methods for creating special instances of routes called filters. Before filters
731
+ * run before any matching route is run, and after filters run after any matched route has run.
732
+ * A before filter can return false to halt the running of any matched routes or other before filters.
733
+ *
734
+ * A filter can take an optional path to match on, or without a path will match every request.
735
+ *
736
+ * ### Example
737
+ *
738
+ * app.before('/foo/:id', function (req) {
739
+ * // will only run before request matching '/foo/:id'
740
+ * })
741
+ *
742
+ * app.before(function (req) {
743
+ * // will run before all routes
744
+ * })
745
+ *
746
+ * app.after('/foo/:id', function (req) {
747
+ * // will only run after routes matching '/foo/:id'
748
+ * })
749
+ *
750
+ * app.after(function (req) {
751
+ * // will run after all routes
752
+ * })
753
+ *
754
+ * Another special kind of route, called state routes, are also generated using the router. State routes
755
+ * are for requests that will not change the current page location. Instead the page location will remain
756
+ * the same but the current state of the page has changed. This allows for states which the server will not
757
+ * be expected to know about and support.
758
+ *
759
+ * ### Example
760
+ *
761
+ * app.state('/foo/:id', function (req) {
762
+ * // will run when the app transitions into the '/foo/:id' state.
763
+ * })
764
+ *
765
+ * Using the `trans` method an app can transition to these kind of states without changing the url location.
766
+ *
767
+ * For convinience routes can be defined within a common base scope, this is useful for keeping your route
768
+ * definitions simpler and DRYer. A scope can either cover the whole app, or just a subset of the routes.
769
+ *
770
+ * ### Example
771
+ *
772
+ * app.scope('/foo', function () {
773
+ * this.get('/:id', function () {
774
+ * // will run for routes that match '/foo/:id'
775
+ * })
776
+ * })
777
+ *
778
+ * @module
779
+ */
780
+ Davis.router = function () {
781
+
782
+ /**
783
+ * Low level method for adding routes to your application.
784
+ *
785
+ * If called with just a method will return a partially applied function that can create routes with
786
+ * that method. This is used internally to provide shortcuts for get, post, put, delete and state
787
+ * routes.
788
+ *
789
+ * You normally want to use the higher level methods such as get and post, but this can be useful for extending
790
+ * Davis to work with other kinds of requests.
791
+ *
792
+ * Example:
793
+ *
794
+ * app.route('get', '/foo', function (req) {
795
+ * // will run when a get request is made to '/foo'
796
+ * })
797
+ *
798
+ * app.patch = app.route('patch') // will return a function that can be used to handle requests with method of patch.
799
+ * app.patch('/bar', function (req) {
800
+ * // will run when a patch request is made to '/bar'
801
+ * })
802
+ *
803
+ * @param {String} method The method for this route.
804
+ * @param {String} path The path for this route.
805
+ * @param {Function} handler The handler for this route, will be called with the request that triggered the route.
806
+ * @returns {Davis.Route} the route that has just been created and added to the route list.
807
+ * @memberOf router
808
+ */
809
+ this.route = function (method, path) {
810
+ var createRoute = function (path) {
811
+ var handlers = Davis.utils.toArray(arguments, 1),
812
+ scope = scopePaths.join(''),
813
+ fullPath, route
814
+
815
+ (typeof path == 'string') ? fullPath = scope + path : fullPath = path
816
+
817
+ route = new Davis.Route (method, fullPath, handlers)
818
+
819
+ routeCollection.push(route)
820
+ return route
821
+ }
822
+
823
+ return (arguments.length == 1) ? createRoute : createRoute.apply(this, Davis.utils.toArray(arguments, 1))
824
+ }
825
+
826
+ /**
827
+ * A convinience wrapper around `app.route` for creating get routes.
828
+ *
829
+ * @param {String} path The path for this route.
830
+ * @param {Function} handler The handler for this route, will be called with the request that triggered the route.
831
+ * @returns {Davis.Route} the route that has just been created and added to the route list.
832
+ * @see Davis.router.route
833
+ * @memberOf router
834
+ */
835
+ this.get = this.route('get')
836
+
837
+ /**
838
+ * A convinience wrapper around `app.route` for creating post routes.
839
+ *
840
+ * @param {String} path The path for this route.
841
+ * @param {Function} handler The handler for this route, will be called with the request that triggered the route.
842
+ * @returns {Davis.Route} the route that has just been created and added to the route list.
843
+ * @see Davis.router.route
844
+ * @memberOf router
845
+ */
846
+ this.post = this.route('post')
847
+
848
+ /**
849
+ * A convinience wrapper around `app.route` for creating put routes.
850
+ *
851
+ * @param {String} path The path for this route.
852
+ * @param {Function} handler The handler for this route, will be called with the request that triggered the route.
853
+ * @returns {Davis.Route} the route that has just been created and added to the route list.
854
+ * @see Davis.router.route
855
+ * @memberOf router
856
+ */
857
+ this.put = this.route('put')
858
+
859
+ /**
860
+ * A convinience wrapper around `app.route` for creating delete routes.
861
+ *
862
+ * delete is a reserved word in javascript so use the `del` method when creating a Davis.Route with a method of delete.
863
+ *
864
+ * @param {String} path The path for this route.
865
+ * @param {Function} handler The handler for this route, will be called with the request that triggered the route.
866
+ * @returns {Davis.Route} the route that has just been created and added to the route list.
867
+ * @see Davis.router.route
868
+ * @memberOf router
869
+ */
870
+ this.del = this.route('delete')
871
+
872
+ /**
873
+ * Adds a state route into the apps route collection.
874
+ *
875
+ * These special kind of routes are not triggered by clicking links or submitting forms, instead they
876
+ * are triggered manually by calling `trans`.
877
+ *
878
+ * Routes added using the state method act in the same way as other routes except that they generate
879
+ * a route that is listening for requests that will not change the page location.
880
+ *
881
+ * Example:
882
+ *
883
+ * app.state('/foo/:id', function (req) {
884
+ * // will run when the app transitions into the '/foo/:id' state.
885
+ * })
886
+ *
887
+ * @param {String} path The path for this route, this will never be seen in the url bar.
888
+ * @param {Function} handler The handler for this route, will be called with the request that triggered the route
889
+ * @memberOf router
890
+ *
891
+ */
892
+ this.state = this.route('state');
893
+
894
+ /**
895
+ * Modifies the scope of the router.
896
+ *
897
+ * If you have many routes that share a common path prefix you can use scope to reduce repeating
898
+ * that path prefix.
899
+ *
900
+ * You can use `scope` in two ways, firstly you can set the scope for the whole app by calling scope
901
+ * before defining routes. You can also provide a function to the scope method, and the scope will
902
+ * only apply to those routes defined within this function. It is also possible to nest scopes within
903
+ * other scopes.
904
+ *
905
+ * Example
906
+ *
907
+ * // using scope with a function
908
+ * app.scope('/foo', function () {
909
+ * this.get('/bar', function (req) {
910
+ * // this route will have a path of '/foo/bar'
911
+ * })
912
+ * })
913
+ *
914
+ * // setting a global scope for the rest of the application
915
+ * app.scope('/bar')
916
+ *
917
+ * // using scope with a function
918
+ * app.scope('/foo', function () {
919
+ * this.scope('/bar', function () {
920
+ * this.get('/baz', function (req) {
921
+ * // this route will have a path of '/foo/bar/baz'
922
+ * })
923
+ * })
924
+ * })
925
+ *
926
+ * @memberOf router
927
+ * @param {String} path The prefix to use as the scope
928
+ * @param {Function} fn A function that will be executed with the router as its context and the path
929
+ * as a prefix
930
+ *
931
+ */
932
+ this.scope = function (path, fn) {
933
+ scopePaths.push(path)
934
+ if (arguments.length == 1) return
935
+
936
+ fn.call(this, this)
937
+ scopePaths.pop()
938
+ }
939
+
940
+ /**
941
+ * Transitions the app into the state identified by the passed path parameter.
942
+ *
943
+ * This allows the app to enter states without changing the page path through a link click or form submit.
944
+ * If there are handlers registered for this state, added by the `state` method, they will be triggered.
945
+ *
946
+ * This method generates a request with a method of 'state', in all other ways this request is identical
947
+ * to those that are generated when clicking links etc.
948
+ *
949
+ * States transitioned to using this method will not be able to be revisited directly with a page load as
950
+ * there is no url that represents the state.
951
+ *
952
+ * An optional second parameter can be passed which will be available to any handlers in the requests
953
+ * params object.
954
+ *
955
+ * Example
956
+ *
957
+ * app.trans('/foo/1')
958
+ *
959
+ * app.trans('/foo/1', {
960
+ * "bar": "baz"
961
+ * })
962
+ *
963
+ *
964
+ * @param {String} path The path that represents this state. This will not be seen in the url bar.
965
+ * @param {Object} data Any additional data that should be sent with the request as params.
966
+ * @memberOf router
967
+ */
968
+ this.trans = function (path, data) {
969
+ if (data) {
970
+ var fullPath = [path, decodeURIComponent(Davis.$.param(data))].join('?')
971
+ } else {
972
+ var fullPath = path
973
+ };
974
+
975
+ var req = new Davis.Request({
976
+ method: 'state',
977
+ fullPath: fullPath,
978
+ title: ''
979
+ })
980
+
981
+ Davis.location.assign(req)
982
+ }
983
+
984
+ /*!
985
+ * Generating convinience methods for creating filters using Davis.Routes and methods to
986
+ * lookup filters.
987
+ */
988
+ this.filter = function (filterName) {
989
+ return function () {
990
+ var method = /.+/;
991
+
992
+ if (arguments.length == 1) {
993
+ var path = /.+/;
994
+ var handler = arguments[0];
995
+ } else if (arguments.length == 2) {
996
+ var path = scopePaths.join('') + arguments[0];
997
+ var handler = arguments[1];
998
+ };
999
+
1000
+ var route = new Davis.Route (method, path, handler)
1001
+ filterCollection[filterName].push(route);
1002
+ return route
1003
+ }
1004
+ }
1005
+
1006
+ this.lookupFilter = function (filterType) {
1007
+ return function (method, path) {
1008
+ return Davis.utils.filter(filterCollection[filterType], function (route) {
1009
+ return route.match(method, path)
1010
+ });
1011
+ }
1012
+ }
1013
+
1014
+ /**
1015
+ * A convinience wrapper around `app.filter` for creating before filters.
1016
+ *
1017
+ * @param {String} path The optionl path for this filter.
1018
+ * @param {Function} handler The handler for this filter, will be called with the request that triggered the route.
1019
+ * @returns {Davis.Route} the route that has just been created and added to the route list.
1020
+ * @memberOf router
1021
+ */
1022
+ this.before = this.filter('before')
1023
+
1024
+ /**
1025
+ * A convinience wrapper around `app.filter` for creating after filters.
1026
+ *
1027
+ * @param {String} path The optionl path for this filter.
1028
+ * @param {Function} handler The handler for this filter, will be called with the request that triggered the route.
1029
+ * @returns {Davis.Route} the route that has just been created and added to the route list.
1030
+ * @memberOf router
1031
+ */
1032
+ this.after = this.filter('after')
1033
+
1034
+ /**
1035
+ * A convinience wrapper around `app.lookupFilter` for looking up before filters.
1036
+ *
1037
+ * @param {String} path The optionl path for this filter.
1038
+ * @param {Function} handler The handler for this filter, will be called with the request that triggered the route.
1039
+ * @returns {Davis.Route} the route that has just been created and added to the route list.
1040
+ * @memberOf router
1041
+ */
1042
+ this.lookupBeforeFilter = this.lookupFilter('before')
1043
+
1044
+ /**
1045
+ * A convinience wrapper around `app.lookupFilter` for looking up after filters.
1046
+ *
1047
+ * @param {String} path The optionl path for this filter.
1048
+ * @param {Function} handler The handler for this filter, will be called with the request that triggered the route.
1049
+ * @returns {Davis.Route} the route that has just been created and added to the route list.
1050
+ * @memberOf router
1051
+ */
1052
+ this.lookupAfterFilter = this.lookupFilter('after')
1053
+
1054
+ /*!
1055
+ * collections of routes and filters
1056
+ * @private
1057
+ */
1058
+ var routeCollection = [];
1059
+ var filterCollection = {
1060
+ before: [],
1061
+ after: []
1062
+ };
1063
+ var scopePaths = []
1064
+
1065
+ /**
1066
+ * Looks for the first route that matches the method and path from a request.
1067
+ * Will only find and return the first matched route.
1068
+ *
1069
+ * @param {String} method the method to use when looking up a route
1070
+ * @param {String} path the path to use when looking up a route
1071
+ * @returns {Davis.Route} route
1072
+ * @memberOf router
1073
+ */
1074
+ this.lookupRoute = function (method, path) {
1075
+ return Davis.utils.filter(routeCollection, function (route) {
1076
+ return route.match(method, path)
1077
+ })[0];
1078
+ };
1079
+ }
1080
+ /*!
1081
+ * Davis - history
1082
+ * Copyright (C) 2011 Oliver Nightingale
1083
+ * MIT Licensed
1084
+ */
1085
+
1086
+ /**
1087
+ * A module to normalize and enhance the window.pushState method and window.onpopstate event.
1088
+ *
1089
+ * Adds the ability to bind to whenever a new state is pushed onto the history stack and normalizes
1090
+ * both of these events into an onChange event.
1091
+ *
1092
+ * @module
1093
+ */
1094
+ Davis.history = (function () {
1095
+
1096
+ /*!
1097
+ * storage for the push state handlers
1098
+ * @private
1099
+ */
1100
+ var pushStateHandlers = [];
1101
+
1102
+ /*!
1103
+ * keep track of whether or not webkit like browsers have fired their initial
1104
+ * page load popstate
1105
+ * @private
1106
+ */
1107
+ var popped = false
1108
+
1109
+ /*!
1110
+ * Add a handler to the push state event. This event is not a native event but is fired
1111
+ * every time a call to pushState is called.
1112
+ *
1113
+ * @param {Function} handler
1114
+ * @private
1115
+ */
1116
+ function onPushState(handler) {
1117
+ pushStateHandlers.push(handler);
1118
+ };
1119
+
1120
+ /*!
1121
+ * Simple wrapper for the native onpopstate event.
1122
+ *
1123
+ * @param {Function} handler
1124
+ * @private
1125
+ */
1126
+ function onPopState(handler) {
1127
+ window.addEventListener('popstate', handler, true);
1128
+ };
1129
+
1130
+ /*!
1131
+ * returns a handler that wraps the native event given onpopstate.
1132
+ * When the page first loads or going back to a time in the history that was not added
1133
+ * by pushState the event.state object will be null. This generates a request for the current
1134
+ * location in those cases
1135
+ *
1136
+ * @param {Function} handler
1137
+ * @private
1138
+ */
1139
+ function wrapped(handler) {
1140
+ return function (event) {
1141
+ if (event.state && event.state._davis) {
1142
+ handler(new Davis.Request(event.state._davis))
1143
+ } else {
1144
+ if (popped) handler(Davis.Request.forPageLoad())
1145
+ };
1146
+ popped = true
1147
+ }
1148
+ }
1149
+
1150
+ /*!
1151
+ * provide a wrapper for any data that is going to be pushed into the history stack. All
1152
+ * data is wrapped in a "_davis" namespace.
1153
+ * @private
1154
+ */
1155
+ function wrapStateData(data) {
1156
+ return {"_davis": data}
1157
+ }
1158
+
1159
+ /**
1160
+ * Bind to the history on change event.
1161
+ *
1162
+ * This is not a native event but is fired any time a new state is pushed onto the history stack,
1163
+ * the current history is replaced or a state is popped off the history stack.
1164
+ * The handler function will be called with a request param which is an instance of Davis.Request.
1165
+ *
1166
+ * @param {Function} handler a function that will be called on push and pop state.
1167
+ * @see Davis.Request
1168
+ * @memberOf history
1169
+ */
1170
+ function onChange(handler) {
1171
+ onPushState(handler);
1172
+ onPopState(wrapped(handler));
1173
+ };
1174
+
1175
+ /*!
1176
+ * returns a function for manipulating the history state and optionally calling any associated
1177
+ * pushStateHandlers
1178
+ *
1179
+ * @param {String} methodName the name of the method to manipulate the history state with.
1180
+ * @private
1181
+ */
1182
+ function changeStateWith (methodName) {
1183
+ return function (request, opts) {
1184
+ popped = true
1185
+ history[methodName](wrapStateData(request.toJSON()), request.title, request.location());
1186
+ if (opts && opts.silent) return
1187
+ Davis.utils.forEach(pushStateHandlers, function (handler) {
1188
+ handler(request);
1189
+ });
1190
+ }
1191
+ }
1192
+
1193
+ /**
1194
+ * Pushes a request onto the history stack.
1195
+ *
1196
+ * This is used internally by Davis to push a new request
1197
+ * resulting from either a form submit or a link click onto the history stack, it will also trigger
1198
+ * the onpushstate event.
1199
+ *
1200
+ * An instance of Davis.Request is expected to be passed, however any object that has a title
1201
+ * and a path property will also be accepted.
1202
+ *
1203
+ * @param {Davis.Request} request the location to be assinged as the current location.
1204
+ * @memberOf history
1205
+ */
1206
+ var assign = changeStateWith('pushState')
1207
+
1208
+ /**
1209
+ * Replace the current state on the history stack.
1210
+ *
1211
+ * This is used internally by Davis when performing a redirect. This will trigger an onpushstate event.
1212
+ *
1213
+ * An instance of Davis.Request is expected to be passed, however any object that has a title
1214
+ * and a path property will also be accepted.
1215
+ *
1216
+ * @param {Davis.Request} request the location to replace the current location with.
1217
+ * @memberOf history
1218
+ */
1219
+ var replace = changeStateWith('replaceState')
1220
+
1221
+ /**
1222
+ * Returns the current location for the application.
1223
+ *
1224
+ * Davis.location delegates to this method for getting the apps current location.
1225
+ *
1226
+ * @memberOf history
1227
+ */
1228
+ function current() {
1229
+ return window.location.pathname + (window.location.search ? window.location.search : '')
1230
+ }
1231
+
1232
+ /*!
1233
+ * Exposing the public methods of this module
1234
+ * @private
1235
+ */
1236
+ return {
1237
+ onChange: onChange,
1238
+ current: current,
1239
+ assign: assign,
1240
+ replace: replace
1241
+ }
1242
+ })()
1243
+ /*!
1244
+ * Davis - location
1245
+ * Copyright (C) 2011 Oliver Nightingale
1246
+ * MIT Licensed
1247
+ */
1248
+
1249
+ /**
1250
+ * A module that acts as a delegator to any locationDelegate implementation. This abstracts the details of
1251
+ * what is being used for the apps routing away from the rest of the library. This allows any kind of routing
1252
+ * To be used with Davis as long as it can respond appropriatly to the given delegate methods.
1253
+ *
1254
+ * A routing module must respond to the following methods
1255
+ *
1256
+ * * __current__ : Should return the current location for the app
1257
+ * * __assign__ : Should set the current location of the app based on the location of the passed request.
1258
+ * * __replace__ : Should at least change the current location to the location of the passed request, for full compatibility it should not add any extra items in the history stack.
1259
+ * * __onChange__ : Should add calbacks that will be fired whenever the location is changed.
1260
+ *
1261
+ * @module
1262
+ *
1263
+ */
1264
+ Davis.location = (function () {
1265
+
1266
+ /*!
1267
+ * By default the Davis uses the Davis.history module for its routing, this gives HTML5 based pushState routing
1268
+ * which is preferrable over location.hash based routing.
1269
+ */
1270
+ var locationDelegate = Davis.history
1271
+
1272
+ /**
1273
+ * Sets the current location delegate.
1274
+ *
1275
+ * The passed delegate will be used for all Davis apps. The delegate
1276
+ * must respond to the following four methods `current`, `assign`, `replace` & `onChange`.
1277
+ *
1278
+ * @param {Object} the location delegate to use.
1279
+ * @memberOf location
1280
+ */
1281
+ function setLocationDelegate(delegate) {
1282
+ locationDelegate = delegate
1283
+ }
1284
+
1285
+ /**
1286
+ * Delegates to the locationDelegate.current method.
1287
+ *
1288
+ * This should return the current location of the app.
1289
+ *
1290
+ * @memberOf location
1291
+ */
1292
+ function current() {
1293
+ return locationDelegate.current()
1294
+ }
1295
+
1296
+ /*!
1297
+ * Creates a function which sends the location delegate the passed message name.
1298
+ * It handles converting a string path to an actual request
1299
+ *
1300
+ * @returns {Function} a function that calls the location delegate with the supplied method name
1301
+ * @memberOf location
1302
+ * @private
1303
+ */
1304
+ function sendLocationDelegate (methodName) {
1305
+ return function (req) {
1306
+ if (typeof req == 'string') req = new Davis.Request (req)
1307
+ locationDelegate[methodName](req)
1308
+ }
1309
+ }
1310
+
1311
+ /**
1312
+ * Delegates to the locationDelegate.assign method.
1313
+ *
1314
+ * This should set the current location for the app to that of the passed request object.
1315
+ *
1316
+ * Can take either a Davis.Request or a string representing the path of the request to assign.
1317
+ *
1318
+ *
1319
+ *
1320
+ * @param {Request} req the request to replace the current location with, either a string or a Davis.Request.
1321
+ * @see Davis.Request
1322
+ * @memberOf location
1323
+ */
1324
+ var assign = sendLocationDelegate('assign')
1325
+
1326
+ /**
1327
+ * Delegates to the locationDelegate.replace method.
1328
+ *
1329
+ * This should replace the current location with that of the passed request.
1330
+ * Ideally it should not create a new entry in the browsers history.
1331
+ *
1332
+ * Can take either a Davis.Request or a string representing the path of the request to assign.
1333
+ *
1334
+ * @param {Request} req the request to replace the current location with, either a string or a Davis.Request.
1335
+ * @see Davis.Request
1336
+ * @memberOf location
1337
+ */
1338
+ var replace = sendLocationDelegate('replace')
1339
+
1340
+ /**
1341
+ * Delegates to the locationDelegate.onChange method.
1342
+ *
1343
+ * This should add a callback that will be called any time the location changes.
1344
+ * The handler function will be called with a request param which is an instance of Davis.Request.
1345
+ *
1346
+ * @param {Function} handler callback function to be called on location chnage.
1347
+ * @see Davis.Request
1348
+ * @memberOf location
1349
+ *
1350
+ */
1351
+ function onChange(handler) {
1352
+ locationDelegate.onChange(handler)
1353
+ }
1354
+
1355
+ /*!
1356
+ * Exposing the public methods of this module
1357
+ * @private
1358
+ */
1359
+ return {
1360
+ setLocationDelegate: setLocationDelegate,
1361
+ current: current,
1362
+ assign: assign,
1363
+ replace: replace,
1364
+ onChange: onChange
1365
+ }
1366
+ })()/*!
1367
+ * Davis - Request
1368
+ * Copyright (C) 2011 Oliver Nightingale
1369
+ * MIT Licensed
1370
+ */
1371
+
1372
+ Davis.Request = (function () {
1373
+
1374
+ /**
1375
+ * Davis.Requests are created from click and submit events. Davis.Requests are passed to Davis.Routes
1376
+ * and are stored in the history stack. They are instantiated by the Davis.listener module.
1377
+ *
1378
+ * A request will have a params object which will contain all query params and form params, any named
1379
+ * params in a routes path will also be added to the requests params object. Also included is support
1380
+ * for rails style nested form params.
1381
+ *
1382
+ * By default the request method will be taken from the method attribute for forms or will be defaulted
1383
+ * to 'get' for links, however there is support for using a hidden field called _method in your forms
1384
+ * to set the correct reqeust method.
1385
+ *
1386
+ * Simple get requests can be created by just passing a path when initializing a request, to set the method
1387
+ * or title you have to pass in an object.
1388
+ *
1389
+ * Each request will have a timestamp property to make it easier to determine if the application is moving
1390
+ * forward or backward through the history stack.
1391
+ *
1392
+ * Example
1393
+ *
1394
+ * var request = new Davis.Request ("/foo/12")
1395
+ *
1396
+ * var request = new Davis.Request ("/foo/12", {title: 'foo', method: 'POST'})
1397
+ *
1398
+ * var request = new Davis.Request({
1399
+ * title: "foo",
1400
+ * fullPath: "/foo/12",
1401
+ * method: "get"
1402
+ * })
1403
+ *
1404
+ * @constructor
1405
+ * @param {String} fullPath
1406
+ * @param {Object} opts An optional object with a title or method proprty
1407
+ *
1408
+ */
1409
+ var Request = function (fullPath, opts) {
1410
+ if (typeof fullPath == 'object') {
1411
+ opts = fullPath
1412
+ fullPath = opts.fullPath
1413
+ delete opts.fullPath
1414
+ }
1415
+
1416
+ var raw = Davis.$.extend({}, {
1417
+ title: "",
1418
+ fullPath: fullPath,
1419
+ method: "get",
1420
+ timestamp: +new Date ()
1421
+ }, opts)
1422
+
1423
+ var self = this;
1424
+ this.raw = raw;
1425
+ this.params = {};
1426
+ this.title = raw.title;
1427
+ this.queryString = raw.fullPath.split("?")[1];
1428
+ this.timestamp = raw.timestamp;
1429
+ this._staleCallback = function () {};
1430
+
1431
+ if (this.queryString) {
1432
+ Davis.utils.forEach(this.queryString.split("&"), function (keyval) {
1433
+ var paramName = keyval.split("=")[0],
1434
+ paramValue = keyval.split("=")[1],
1435
+ nestedParamRegex = /^(\w+)\[(\w+)?\](\[\])?/,
1436
+ nested;
1437
+ if (nested = nestedParamRegex.exec(paramName)) {
1438
+ var paramParent = nested[1];
1439
+ var paramName = nested[2];
1440
+ var isArray = !!nested[3];
1441
+ var parentParams = self.params[paramParent] || {};
1442
+
1443
+ if (isArray) {
1444
+ parentParams[paramName] = parentParams[paramName] || [];
1445
+ parentParams[paramName].push(decodeURIComponent(paramValue));
1446
+ self.params[paramParent] = parentParams;
1447
+ } else if (!paramName && !isArray) {
1448
+ parentParams = self.params[paramParent] || []
1449
+ parentParams.push(decodeURIComponent(paramValue))
1450
+ self.params[paramParent] = parentParams
1451
+ } else {
1452
+ parentParams[paramName] = decodeURIComponent(paramValue);
1453
+ self.params[paramParent] = parentParams;
1454
+ }
1455
+ } else {
1456
+ self.params[paramName] = decodeURIComponent(paramValue);
1457
+ };
1458
+
1459
+ });
1460
+ };
1461
+
1462
+ raw.fullPath = raw.fullPath.replace(/^https?:\/\/.+?\//, '/');
1463
+
1464
+ this.method = (this.params._method || raw.method).toLowerCase();
1465
+
1466
+ this.path = raw.fullPath
1467
+ .replace(/\?.+$/, "") // Remove the query string
1468
+ .replace(/^https?:\/\/[^\/]+/, ""); // Remove the protocol and host parts
1469
+
1470
+ this.fullPath = raw.fullPath;
1471
+
1472
+ this.delegateToServer = raw.delegateToServer || Davis.noop;
1473
+ this.isForPageLoad = raw.forPageLoad || false;
1474
+
1475
+ if (Request.prev) Request.prev.makeStale(this);
1476
+ Request.prev = this;
1477
+
1478
+ };
1479
+
1480
+ /**
1481
+ * Redirects the current request to a new location.
1482
+ *
1483
+ * Calling redirect on an instance of Davis.Request will create a new request using the path and
1484
+ * title of the current request. Redirected requests always have a method of 'get'.
1485
+ *
1486
+ * The request created will replace the current request in the history stack. Redirect is most
1487
+ * often useful inside a handler for a form submit. After succesfully handling the form the app
1488
+ * can redirect to another path. This means that the current form will not be re-submitted if
1489
+ * navigating through the history with the back or forward buttons because the request that the
1490
+ * submit generated has been replaced in the history stack.
1491
+ *
1492
+ * Example
1493
+ *
1494
+ * this.post('/foo', function (req) {
1495
+ * processFormRequest(req.params) // do something with the form request
1496
+ * req.redirect('/bar');
1497
+ * })
1498
+ *
1499
+ * @param {String} path The path to redirect the current request to
1500
+ * @memberOf Request
1501
+ */
1502
+ Request.prototype.redirect = function (path) {
1503
+ Davis.location.replace(new Request ({
1504
+ method: 'get',
1505
+ fullPath: path,
1506
+ title: this.title
1507
+ }));
1508
+ };
1509
+
1510
+ /**
1511
+ * Adds a callback to be called when the request is stale.
1512
+ * A request becomes stale when it is no longer the current request, this normally occurs when a
1513
+ * new request is triggered. A request can be marked as stale manually if required. The callback
1514
+ * passed to whenStale will be called with the new request that is making the current request stale.
1515
+ *
1516
+ * Use the whenStale callback to 'teardown' the objects required for the current route, this gives
1517
+ * a chance for views to hide themselves and unbind any event handlers etc.
1518
+ *
1519
+ * Example
1520
+ *
1521
+ * this.get('/foo', function (req) {
1522
+ * var fooView = new FooView ()
1523
+ * fooView.render() // display the foo view
1524
+ * req.whenStale(function (nextReq) {
1525
+ * fooView.remove() // stop displaying foo view and unbind any events
1526
+ * })
1527
+ * })
1528
+ *
1529
+ * @param {Function} callback A single callback that will be called when the request becomes stale.
1530
+ * @memberOf Request
1531
+ *
1532
+ */
1533
+ Request.prototype.whenStale = function (callback) {
1534
+ this._staleCallback = callback;
1535
+ }
1536
+
1537
+ /**
1538
+ * Mark the request as stale.
1539
+ *
1540
+ * This will cause the whenStale callback to be called.
1541
+ *
1542
+ * @param {Davis.Request} req The next request that has been recieved.
1543
+ * @memberOf Request
1544
+ */
1545
+ Request.prototype.makeStale = function (req) {
1546
+ this._staleCallback.call(req, req);
1547
+ }
1548
+
1549
+ /**
1550
+ * Returns the location or path that should be pushed onto the history stack.
1551
+ *
1552
+ * For get requests this will be the same as the path, for post, put, delete and state requests this will
1553
+ * be blank as no location should be pushed onto the history stack.
1554
+ *
1555
+ * @returns {String} string The location that the url bar should display and should be pushed onto the history stack for this request.
1556
+ * @memberOf Request
1557
+ */
1558
+ Request.prototype.location = function () {
1559
+ return (this.method === 'get') ? this.fullPath : ''
1560
+ }
1561
+
1562
+ /**
1563
+ * Converts the request to a string representation of itself by combining the method and fullPath
1564
+ * attributes.
1565
+ *
1566
+ * @returns {String} string representation of the request
1567
+ * @memberOf Request
1568
+ */
1569
+ Request.prototype.toString = function () {
1570
+ return [this.method.toUpperCase(), this.path].join(" ")
1571
+ };
1572
+
1573
+ /**
1574
+ * Converts the request to a plain object which can be converted to a JSON string.
1575
+ *
1576
+ * Used when pushing a request onto the history stack.
1577
+ *
1578
+ * @returns {Object} a plain object representation of the request.
1579
+ * @memberOf Request
1580
+ */
1581
+ Request.prototype.toJSON = function () {
1582
+ return {
1583
+ title: this.raw.title,
1584
+ fullPath: this.raw.fullPath,
1585
+ method: this.raw.method,
1586
+ timestamp: this.raw.timestamp
1587
+ }
1588
+ }
1589
+
1590
+ /**
1591
+ * Creates a new request for the page on page load.
1592
+ *
1593
+ * This is required because usually requests are generated from clicking links or submitting forms
1594
+ * however this doesn't happen on a page load but should still be considered a request that the
1595
+ * JavaScript app should handle.
1596
+ *
1597
+ * @returns {Davis.Request} A request representing the current page loading.
1598
+ * @memberOf Request
1599
+ */
1600
+ Request.forPageLoad = function () {
1601
+ return new this ({
1602
+ method: 'get',
1603
+ // fullPath: window.location.pathname,
1604
+ fullPath: Davis.location.current(),
1605
+ title: document.title,
1606
+ forPageLoad: true
1607
+ });
1608
+ }
1609
+
1610
+ /*!
1611
+ * Stores the last request
1612
+ * @private
1613
+ */
1614
+ Request.prev = null
1615
+
1616
+ return Request
1617
+
1618
+ })()
1619
+ /*!
1620
+ * Davis - App
1621
+ * Copyright (C) 2011 Oliver Nightingale
1622
+ * MIT Licensed
1623
+ */
1624
+
1625
+ Davis.App = (function () {
1626
+
1627
+ /**
1628
+ * Constructor for Davis.App
1629
+ *
1630
+ * @constructor
1631
+ * @returns {Davis.App}
1632
+ */
1633
+ function App() {
1634
+ this.running = false;
1635
+ this.boundToInternalEvents = false;
1636
+
1637
+ this.use(Davis.listener)
1638
+ this.use(Davis.event)
1639
+ this.use(Davis.router)
1640
+ this.use(Davis.logger)
1641
+ };
1642
+
1643
+ /**
1644
+ * A convinience function for changing the apps default settings.
1645
+ *
1646
+ * Should be used before starting the app to ensure any new settings
1647
+ * are picked up and used.
1648
+ *
1649
+ * Example:
1650
+ *
1651
+ * app.configure(function (config) {
1652
+ * config.linkSelector = 'a.davis'
1653
+ * config.formSelector = 'form.davis'
1654
+ * })
1655
+ *
1656
+ * @param {Function} config This function will be executed with the context bound to the apps setting object, this will also be passed as the first argument to the function.
1657
+ */
1658
+ App.prototype.configure = function(config) {
1659
+ config.call(this.settings, this.settings);
1660
+ };
1661
+
1662
+ /**
1663
+ * Method to include a plugin in this app.
1664
+ *
1665
+ * A plugin is just a function that will be evaluated in the context of the app.
1666
+ *
1667
+ * Example:
1668
+ * app.use(Davis.title)
1669
+ *
1670
+ * @param {Function} plugin The plugin to use
1671
+ *
1672
+ */
1673
+ App.prototype.use = function(plugin) {
1674
+ plugin.apply(this, Davis.utils.toArray(arguments, 1))
1675
+ };
1676
+
1677
+ /**
1678
+ * Method to add helper properties to all requests in the application.
1679
+ *
1680
+ * Helpers will be added to the Davis.Request.prototype. Care should be taken not to override any existing Davis.Request
1681
+ * methods.
1682
+ *
1683
+ * @param {Object} helpers An object containing helpers to mixin to the request
1684
+ */
1685
+ App.prototype.helpers = function(helpers) {
1686
+ for (property in helpers) {
1687
+ if (helpers.hasOwnProperty(property)) Davis.Request.prototype[property] = helpers[property]
1688
+ }
1689
+ };
1690
+
1691
+ /**
1692
+ * Settings for the app. These may be overriden directly or by using the configure
1693
+ * convinience method.
1694
+ *
1695
+ * `linkSelector` is the jquery selector for all the links on the page that you want
1696
+ * Davis to respond to. These links will not trigger a normal http request.
1697
+ *
1698
+ * `formSelector` is similar to link selector but for all the forms that davis will bind to
1699
+ *
1700
+ * `throwErrors` decides whether or not any errors will be caugth by Davis. If this is set to true
1701
+ * errors will be thrown so that the request will not be handled by JavaScript, the server will have
1702
+ * to provide a response. When set to false errors in a route will be caught and the server will not
1703
+ * receive the request.
1704
+ *
1705
+ * `handleRouteNotFound` determines whether or not Davis should handle requests when there is no matching
1706
+ * route. If set to false Davis will allow the request to be passed to your server to handle if no matching
1707
+ * route can be found.
1708
+ *
1709
+ * `generateRequestOnPageLoad` determines whether a request should be generated for the initial page load.
1710
+ * by default this is set to false. A Davis.Request will not be generated with the path of the current
1711
+ * page. Setting this to true will cause a request to be passed to your app for the inital page load.
1712
+ *
1713
+ * @see #configure
1714
+ */
1715
+
1716
+ App.prototype.settings = {
1717
+ linkSelector: 'a',
1718
+ formSelector: 'form',
1719
+ throwErrors: true,
1720
+ handleRouteNotFound: false,
1721
+ generateRequestOnPageLoad: false
1722
+ };
1723
+
1724
+ /**
1725
+ * Starts the app's routing.
1726
+ *
1727
+ * Apps created using the convinience Davis() function are automatically started.
1728
+ *
1729
+ * Starting the app binds all links and forms, so clicks and submits
1730
+ * create Davis requests that will be pushed onto the browsers history stack. Browser history change
1731
+ * events will be picked up and the request that caused the change will be matched against the apps
1732
+ * routes and filters.
1733
+ */
1734
+ App.prototype.start = function(){
1735
+ var self = this;
1736
+
1737
+ if (this.running) return
1738
+
1739
+ if (!Davis.supported()) {
1740
+ this.trigger('unsupported')
1741
+ return
1742
+ };
1743
+
1744
+ var runFilterWith = function (request) {
1745
+ return function (filter) {
1746
+ var result = filter.run(request, request);
1747
+ return (typeof result === "undefined" || result);
1748
+ }
1749
+ }
1750
+
1751
+ var beforeFiltersPass = function (request) {
1752
+ return Davis.utils.every(
1753
+ self.lookupBeforeFilter(request.method, request.path),
1754
+ runFilterWith(request)
1755
+ )
1756
+ }
1757
+
1758
+ var handleRequest = function (request) {
1759
+ if (beforeFiltersPass(request)) {
1760
+ self.trigger('lookupRoute', request)
1761
+ var route = self.lookupRoute(request.method, request.path);
1762
+ if (route) {
1763
+ self.trigger('runRoute', request, route);
1764
+
1765
+ try {
1766
+ route.run(request)
1767
+ self.trigger('routeComplete', request, route)
1768
+ } catch (error) {
1769
+ self.trigger('routeError', request, route, error)
1770
+ }
1771
+
1772
+ Davis.utils.every(
1773
+ self.lookupAfterFilter(request.method, request.path),
1774
+ runFilterWith(request)
1775
+ );
1776
+
1777
+ } else {
1778
+ self.trigger('routeNotFound', request);
1779
+ }
1780
+ } else {
1781
+ self.trigger('requestHalted', request)
1782
+ }
1783
+ }
1784
+
1785
+ var bindToInternalEvents = function () {
1786
+ self
1787
+ .bind('runRoute', function (request) {
1788
+ self.logger.info("runRoute: " + request.toString());
1789
+ })
1790
+ .bind('routeNotFound', function (request) {
1791
+ if (!self.settings.handleRouteNotFound && !request.isForPageLoad) {
1792
+ self.stop()
1793
+ request.delegateToServer()
1794
+ };
1795
+ self.logger.warn("routeNotFound: " + request.toString());
1796
+ })
1797
+ .bind('start', function () {
1798
+ self.logger.info("application started")
1799
+ })
1800
+ .bind('stop', function () {
1801
+ self.logger.info("application stopped")
1802
+ })
1803
+ .bind('routeError', function (request, route, error) {
1804
+ if (self.settings.throwErrors) throw(error)
1805
+ self.logger.error(error.message, error.stack)
1806
+ });
1807
+
1808
+ Davis.location.onChange(function (req) {
1809
+ handleRequest(req)
1810
+ });
1811
+
1812
+ self.boundToInternalEvents = true
1813
+ }
1814
+
1815
+ if (!this.boundToInternalEvents) bindToInternalEvents()
1816
+
1817
+ this.listen();
1818
+ this.trigger('start')
1819
+ this.running = true;
1820
+
1821
+ if (this.settings.generateRequestOnPageLoad) handleRequest(Davis.Request.forPageLoad())
1822
+
1823
+ };
1824
+
1825
+ /**
1826
+ * Stops the app's routing.
1827
+ *
1828
+ * Stops the app listening to clicks and submits on all forms and links found using the current
1829
+ * apps settings.
1830
+ */
1831
+ App.prototype.stop = function() {
1832
+ this.unlisten();
1833
+ this.trigger('stop')
1834
+ this.running = false
1835
+ };
1836
+
1837
+ return App;
1838
+ })()