brancusi 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+ })()