forked from davej/angular-classy
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathangular-classy.coffee
More file actions
194 lines (154 loc) · 6.64 KB
/
angular-classy.coffee
File metadata and controls
194 lines (154 loc) · 6.64 KB
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
188
189
190
191
192
193
194
###
Angular Classy 0.4.2
Dave Jeffery, @DaveJ
License: MIT
###
### global angular ###
'use strict';
defaults =
controller:
addFnsToScope: true
watchObject: true
_scopeName: '$scope'
_scopeShortcut: true
_scopeShortcutName: '$'
_watchKeywords:
objectEquality: ['{object}', '{deep}']
collection: ['{collection}', '{shallow}']
event: ['{event}']
origMethod = angular.module
angular.module = (name, reqs, configFn) ->
###
# We have to monkey-patch the `angular.module` method to see if 'classy' has been specified
# as a requirement. We also need the module name to we can register our classy controllers.
# Unfortunately there doesn't seem to be a more pretty/pluggable way to this.
###
module = origMethod(name, reqs, configFn)
# If this module has required 'classy' then we're going to add `classyController`
if reqs and 'classy' in reqs
module.classy =
options:
controller: {}
controller: (classObj) ->
classObj.__options = angular.extend {}, defaults.controller, module.classy.options.controller, classObj.__options
c = class classyController
# `classyController` contains only a set of proxy functions for `classFns`,
# this is because I suspect that performance is better this way.
# TODO: Test performance to see if this is the most performant way to do it.
__options: classObj.__options
# Create the Classy Controller
classFns.create(module, classObj, @)
constructor: ->
# Where the magic happens
classFns.construct(@, arguments)
for own key,value of classObj
c::[key] = value
return c
module.cC = module.classyController = module.classy.controller
return module
angular.module('classy', []);
classFns =
selectorControllerCount: 0
construct: (parent, args) ->
options = parent.constructor::__options
@bindDependencies(parent, args)
if options.addFnsToScope then @addFnsToScope(parent)
parent.init?()
if options.watchObject and angular.isObject(parent.watch)
@registerWatchers(parent)
addFnsToScope: (parent) ->
# Adds controller functions (unless they have a `_` prefix) to the `$scope`
$scope = parent[parent.constructor::__options._scopeName]
for key, fn of parent.constructor::
continue unless angular.isFunction(fn)
continue if key in ['constructor', 'init', 'watch']
parent[key] = angular.bind(parent, fn)
if key[0] isnt '_'
$scope[key] = parent[key]
bindDependencies: (parent, args) ->
injectObject = parent.__classyControllerInjectObject
injectObjectMode = !!injectObject
options = parent.constructor::__options
# Takes the `$inject` dependencies and assigns a class-wide (`@`) variable to each one.
for key, i in parent.constructor.$inject
if injectObjectMode and (injectName = injectObject[key]) and injectName != '.'
parent[injectName] = args[i]
else
parent[key] = args[i]
if key is options._scopeName and options._scopeShortcut
# Add a shortcut to the $scope (by default `this.$`)
parent[options._scopeShortcutName] = parent[key]
registerWatchers: (parent) ->
# Iterates over the watch object and creates the appropriate `$scope.$watch` listener
$scope = parent[parent.constructor::__options._scopeName]
if !$scope
throw new Error "You need to inject `$scope` to use the watch object"
watchKeywords = parent.constructor::__options._watchKeywords
watchTypes =
normal:
keywords: []
fnCall: (parent, expression, fn) ->
$scope.$watch(expression, angular.bind(parent, fn))
objectEquality:
keywords: watchKeywords.objectEquality
fnCall: (parent, expression, fn) ->
$scope.$watch(expression, angular.bind(parent, fn), true)
collection:
keywords: watchKeywords.collection
fnCall: (parent, expression, fn) ->
$scope.$watchCollection(expression, angular.bind(parent, fn))
event:
keywords: watchKeywords.event
fnCall: (parent, expression, fn) ->
$scope.$on(expression, angular.bind(parent, fn))
for expression, fn of parent.watch
if angular.isString(fn) then fn = parent[fn]
if angular.isString(expression) and angular.isFunction(fn)
watchRegistered = false
# Search for keywords that identify it is a non-standard watch
for watchType of watchTypes
if watchRegistered then break
for keyword in watchTypes[watchType].keywords
if watchRegistered then break
if expression.indexOf(keyword) isnt -1
watchTypes[watchType].fnCall(parent, expression.replace(keyword, ''), fn)
watchRegistered = true
# If no keywords have been found then register it as a normal watch
if !watchRegistered then watchTypes.normal.fnCall(parent, expression, fn)
inject: (parent, deps) ->
if angular.isObject deps[0]
parent::__classyControllerInjectObject = injectObject = deps[0]
deps = (service for service, name of injectObject)
scopeName = parent::__options._scopeName
if injectObject?[scopeName] and injectObject[scopeName] != '.'
parent::__options._scopeName = injectObject[scopeName]
# Add the `deps` to the controller's $inject annotations.
parent.$inject = deps
registerSelector: (appInstance, selector, parent) ->
@selectorControllerCount++
controllerName = "ClassySelector#{@selectorControllerCount}Controller"
appInstance.controller controllerName, parent
if angular.isElement(selector)
selector.setAttribute('data-ng-controller', controllerName)
return
if angular.isString(selector)
# Query the dom using jQuery if available, otherwise fallback to qSA
els = window.jQuery?(selector) or document.querySelectorAll(selector)
else if angular.isArray(selector)
els = selector
else return
for el in els
if angular.isElement(el)
el.setAttribute('data-ng-controller', controllerName)
create: (appInstance, classObj, parent) ->
if classObj.el || classObj.selector
# Register the controller using selector
@registerSelector(appInstance, classObj.el || classObj.selector, parent)
if angular.isString(classObj.name)
# Register the controller using name
appInstance.controller classObj.name, parent
deps = classObj.inject
# Inject the `deps` if it's passed in as an array
if angular.isArray(deps) then @inject(parent, deps)
# If `deps` is object: Wrap object in array and then inject
else if angular.isObject(deps) then @inject(parent, [deps])