This repository was archived by the owner on Nov 9, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcontroller.coffee
187 lines (144 loc) · 6.22 KB
/
controller.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
{BatmanObject, SimpleHash, developer, typeOf, functionName} = require 'foundation'
{helpers, LifecycleEvents} = require 'utilities'
RenderCache = require './render_cache'
ControllerActionFrame = require './controller_action_frame'
module.exports = class Controller extends BatmanObject
coerceIntegerParams: true
@singleton 'sharedController'
@wrapAccessor 'routingKey', (core) ->
get: ->
if @routingKey?
@routingKey
else
developer.error("Please define `routingKey` on the prototype of #{functionName(@constructor)} in order for your controller to be minification safe.") if Batman.config.minificationErrors
functionName(@constructor).replace(/Controller$/, '')
@classMixin LifecycleEvents
@lifecycleEvent 'action', (options = {}) ->
normalized = {}
only = if typeOf(options.only) is 'String' then [options.only] else options.only
except = if typeOf(options.except) is 'String' then [options.except] else options.except
normalized.if = (params, frame) ->
return false if @_afterFilterRedirect
return false if only and frame.action not in only
return false if except and frame.action in except
return true
return normalized
@beforeFilter: ->
developer.deprecated("Batman.Controller::beforeFilter", "Please use beforeAction instead.")
@beforeAction.apply(this, arguments)
@afterFilter: ->
developer.deprecated("Batman.Controller::afterFilter", "Please use afterAction instead.")
@afterAction.apply(this, arguments)
@afterAction (params) ->
if @autoScrollToHash && params['#']?
@scrollToHash(params['#'])
@catchError: (errors..., options) ->
@::initializeBatman()
@_batman.errorHandlers ||= new SimpleHash
handlers = if typeOf(options.with) is 'Array' then options.with else [options.with]
for error in errors
currentHandlers = @_batman.errorHandlers.get(error) || []
@_batman.errorHandlers.set(error, currentHandlers.concat(handlers))
errorHandler: (callback) =>
errorFrame = @_actionFrames?[@_actionFrames.length - 1]
(err, result, env) =>
if err
return if errorFrame?.error
errorFrame?.error = err
throw err if not @handleError(err)
else
callback?(result, env)
handleError: (error) =>
handled = false
@constructor._batman.getAll('errorHandlers')?.forEach (hash) =>
hash.forEach (key, value) =>
if error instanceof key
handled = true
for handler in value
handler = @[handler] if typeof handler is 'string'
handler.call(this, error)
handled
constructor: ->
super
@_resetActionFrames()
renderCache: new RenderCache
defaultRenderYield: 'main'
autoScrollToHash: true
# You shouldn't call this method directly. It will be called by the dispatcher when a route is called.
# If you need to call a route manually, use `Batman.redirect()`.
dispatch: (action, params = {}) ->
if @coerceIntegerParams
for key, value of params
params[key] = helpers.coerceInteger(value)
params.controller ||= @get 'routingKey'
params.action ||= action
params.target ||= @
@_resetActionFrames()
@set 'action', action
@set 'params', params
@executeAction(action, params)
redirectTo = @_afterFilterRedirect
@_afterFilterRedirect = null
delete @_afterFilterRedirect
Batman.redirect(redirectTo) if redirectTo
executeAction: (action, params = @get('params')) ->
developer.assert @[action], "Error! Controller action #{@get('routingKey')}.#{action} couldn't be found!"
parentFrame = @_actionFrames[@_actionFrames.length - 1]
frame = new ControllerActionFrame {parentFrame, action, params}, =>
@fireLifecycleEvent('afterAction', frame.params, frame) if not @_afterFilterRedirect
@_resetActionFrames()
Batman.navigator?.redirect = oldRedirect
@_actionFrames.push(frame)
frame.startOperation(internal: true)
oldRedirect = Batman.navigator?.redirect
Batman.navigator?.redirect = @redirect
if @fireLifecycleEvent('beforeAction', frame.params, frame) != false
result = @[action](params) if not @_afterFilterRedirect
@render() if not frame.operationOccurred
frame.finishOperation()
return result
redirect: (url) =>
frame = @_actionFrames[@_actionFrames.length - 1]
if frame
if frame.operationOccurred
developer.warn "Warning! Trying to redirect but an action has already been taken during #{@get('routingKey')}.#{frame.action || @get('action')}"
return
frame.startAndFinishOperation()
if @_afterFilterRedirect?
developer.warn "Warning! Multiple actions trying to redirect!"
else
@_afterFilterRedirect = url
else
if typeOf(url) is 'Object'
url.controller ||= @get('routingKey')
Batman.redirect(url)
render: (options = {}) ->
if frame = @_actionFrames?[@_actionFrames.length - 1]
frame.startOperation()
# Ensure the frame is marked as having had an action executed so that render false prevents the implicit render.
if options is false
frame.finishOperation()
return
action = frame?.action || @get('action')
if view = options.view
options.view = null
else
options.viewClass ||= @_viewClassForAction(action)
options.source ||= options.viewClass?::source || helpers.underscore(@get('routingKey') + '/' + action)
view = @renderCache.viewForOptions(options)
if view
view.once 'viewDidAppear', ->
frame?.finishOperation()
yieldName = options.into || @defaultRenderYield
if yieldContentView = Batman.DOM.Yield.withName(yieldName).contentView
yieldContentView.die() if yieldContentView isnt view and not yieldContentView.isDead
view.set('contentFor', yieldName) if not view.contentFor and not view.parentNode
view.set('controller', this)
Batman.currentApp?.layout?.subviews?.add(view)
@set('currentView', view)
view
scrollToHash: (hash = @get('params')['#'])-> Batman.DOM.scrollIntoView(hash)
_resetActionFrames: -> @_actionFrames = []
_viewClassForAction: (action) ->
classPrefix = @get('routingKey').replace('/', '_')
Batman.currentApp?[helpers.camelize("#{classPrefix}_#{action}_view")] || Batman.View