nali 0.0.2
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.
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/assets/javascripts/nali/application.js.coffee +27 -0
- data/lib/assets/javascripts/nali/collection.js.coffee +147 -0
- data/lib/assets/javascripts/nali/connection.js.coffee +58 -0
- data/lib/assets/javascripts/nali/controller.js.coffee +45 -0
- data/lib/assets/javascripts/nali/cookie.js.coffee +21 -0
- data/lib/assets/javascripts/nali/extensions.js.coffee +16 -0
- data/lib/assets/javascripts/nali/index.js.coffee +10 -0
- data/lib/assets/javascripts/nali/jbone.min.js +10 -0
- data/lib/assets/javascripts/nali/model.js.coffee +237 -0
- data/lib/assets/javascripts/nali/nali.js.coffee +127 -0
- data/lib/assets/javascripts/nali/notice.js.coffee +14 -0
- data/lib/assets/javascripts/nali/router.js.coffee +74 -0
- data/lib/assets/javascripts/nali/view.js.coffee +286 -0
- data/lib/nali.rb +6 -0
- data/lib/nali/application.rb +112 -0
- data/lib/nali/connection.rb +96 -0
- data/lib/nali/controller.rb +68 -0
- data/lib/nali/extensions.rb +11 -0
- data/lib/nali/helpers.rb +18 -0
- data/lib/nali/model.rb +48 -0
- data/lib/nali/version.rb +3 -0
- metadata +279 -0
@@ -0,0 +1,127 @@
|
|
1
|
+
window.Nali =
|
2
|
+
|
3
|
+
sysname: 'Nali'
|
4
|
+
extensions: {}
|
5
|
+
|
6
|
+
starting: ->
|
7
|
+
for name, extension of @extensions
|
8
|
+
extension.runExtensions()
|
9
|
+
extension.initialize() if extension.hasOwnProperty 'initialize'
|
10
|
+
@starting.call extension
|
11
|
+
@
|
12
|
+
|
13
|
+
extend: ( obj ) ->
|
14
|
+
sysname = Object.keys( obj )[0]
|
15
|
+
@[ sysname ] = @extensions[ sysname ] = obj[ sysname ]
|
16
|
+
@[ sysname ].extensions = {}
|
17
|
+
@[ sysname ].sysname = sysname
|
18
|
+
@[ sysname ].__proto__ = @
|
19
|
+
@[ sysname ] :: = @
|
20
|
+
@[ sysname ].initObservation()
|
21
|
+
@[ sysname ]
|
22
|
+
|
23
|
+
clone: ( obj = {} ) ->
|
24
|
+
obj.__proto__ = @
|
25
|
+
obj :: = @
|
26
|
+
obj.initObservation()
|
27
|
+
obj.cloning?()
|
28
|
+
obj
|
29
|
+
|
30
|
+
expand: ( obj ) ->
|
31
|
+
if obj instanceof Object then @[ key ] = value for own key, value of obj
|
32
|
+
else console.error "Expand of %O error - argument is not Object", @
|
33
|
+
@
|
34
|
+
|
35
|
+
copy: ( obj ) ->
|
36
|
+
copy = {}
|
37
|
+
copy[ property ] = value for own property, value of obj
|
38
|
+
copy
|
39
|
+
|
40
|
+
childOf: ( parent ) ->
|
41
|
+
if parent instanceof Object
|
42
|
+
return false unless @::?
|
43
|
+
return true if @:: is parent
|
44
|
+
else
|
45
|
+
return false unless @::?.sysname?
|
46
|
+
return true if @::sysname is parent
|
47
|
+
@::childOf parent
|
48
|
+
|
49
|
+
runExtensions: ( context = @ ) ->
|
50
|
+
@::?.runExtensions context
|
51
|
+
@extension.call context if @hasOwnProperty 'extension'
|
52
|
+
|
53
|
+
getter: ( property, callback ) ->
|
54
|
+
@__defineGetter__ property, callback
|
55
|
+
@
|
56
|
+
|
57
|
+
setter: ( property, callback ) ->
|
58
|
+
@__defineSetter__ property, callback
|
59
|
+
@
|
60
|
+
|
61
|
+
access: ( obj, getter, setter ) ->
|
62
|
+
for own property of obj
|
63
|
+
do( property ) =>
|
64
|
+
@getter property, -> obj[ property ]
|
65
|
+
@setter property, ( value ) -> obj[ property ] = value
|
66
|
+
@
|
67
|
+
|
68
|
+
initObservation: ->
|
69
|
+
@observers = [] unless @hasOwnProperty 'observers'
|
70
|
+
@observables = [] unless @hasOwnProperty 'observables'
|
71
|
+
@
|
72
|
+
|
73
|
+
destroyObservation: ->
|
74
|
+
@unsubscribeAll()
|
75
|
+
@unsubscribeFromAll()
|
76
|
+
@
|
77
|
+
|
78
|
+
addObservationItem: ( to, obj, event, callback ) ->
|
79
|
+
return @ for item in @[ to ] when item[0] is obj and item[1] is event and item[2] in [ undefined, callback ]
|
80
|
+
@[ to ].push if callback then [ obj, event, callback ] else [ obj, event ]
|
81
|
+
@
|
82
|
+
|
83
|
+
removeObservationItem: ( from, obj, event ) ->
|
84
|
+
for item in @[ from ][ 0.. ] when item[0] is obj and ( item[1] is event or not event )
|
85
|
+
@[ from ].splice @[ from ].indexOf( item ), 1
|
86
|
+
@
|
87
|
+
|
88
|
+
subscribe: ( observer, event, callback ) ->
|
89
|
+
@addObservationItem 'observers', observer, event, callback
|
90
|
+
observer.addObservationItem 'observables', @, event, callback
|
91
|
+
@
|
92
|
+
|
93
|
+
subscribeTo: ( observable, event, callback ) ->
|
94
|
+
observable.subscribe @, event, callback
|
95
|
+
@
|
96
|
+
|
97
|
+
subscribeOne: ( observer, event, callback ) ->
|
98
|
+
callbackOne = ( args... ) =>
|
99
|
+
callback.call observer, args...
|
100
|
+
@unsubscribe observer, event
|
101
|
+
@subscribe observer, event, callbackOne
|
102
|
+
@
|
103
|
+
|
104
|
+
subscribeOneTo: ( observable, event, callback ) ->
|
105
|
+
observable.subscribeOne @, event, callback
|
106
|
+
@
|
107
|
+
|
108
|
+
unsubscribe: ( observer, event ) ->
|
109
|
+
@removeObservationItem 'observers', observer, event
|
110
|
+
observer.removeObservationItem 'observables', @, event
|
111
|
+
@
|
112
|
+
|
113
|
+
unsubscribeTo: ( observable, event ) ->
|
114
|
+
observable.unsubscribe @, event
|
115
|
+
@
|
116
|
+
|
117
|
+
unsubscribeAll: ( event ) ->
|
118
|
+
@unsubscribe item[0], event for item in @observers[ 0.. ]
|
119
|
+
@
|
120
|
+
|
121
|
+
unsubscribeFromAll: ( event ) ->
|
122
|
+
@unsubscribeTo item[0], event for item in @observables[ 0.. ]
|
123
|
+
@
|
124
|
+
|
125
|
+
trigger: ( event, args... ) ->
|
126
|
+
item[2].call item[0], args... for item in @observers[ 0.. ] when item[1] is event
|
127
|
+
@
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Nali.Model.extend Notice:
|
2
|
+
initialize: -> @::::Notice = @
|
3
|
+
info: ( params ) -> @build( params ).show 'info'
|
4
|
+
warning: ( params ) -> @build( params ).show 'warning'
|
5
|
+
error: ( params ) -> @build( params ).show 'error'
|
6
|
+
|
7
|
+
Nali.View.extend NoticeInfo:
|
8
|
+
onShow: -> @hide 3000
|
9
|
+
|
10
|
+
Nali.View.extend NoticeWarning:
|
11
|
+
onShow: -> @hide 3000
|
12
|
+
|
13
|
+
Nali.View.extend NoticeError:
|
14
|
+
onShow: -> @hide 3000
|
@@ -0,0 +1,74 @@
|
|
1
|
+
Nali.extend Router:
|
2
|
+
|
3
|
+
initialize: ->
|
4
|
+
@subscribeTo @Connection, 'open', @start
|
5
|
+
@::redirect = ( args... ) => @go args...
|
6
|
+
@
|
7
|
+
|
8
|
+
routes: {}
|
9
|
+
|
10
|
+
start: ->
|
11
|
+
@scanRoutes()
|
12
|
+
@_( window ).on 'popstate', ( event ) =>
|
13
|
+
event.preventDefault()
|
14
|
+
event.stopPropagation()
|
15
|
+
@saveHistory false
|
16
|
+
@go event.target.location.pathname
|
17
|
+
@go()
|
18
|
+
@
|
19
|
+
|
20
|
+
scanRoutes: ->
|
21
|
+
for name, controller of @Controller.extensions when controller.actions?
|
22
|
+
route = '^'
|
23
|
+
route += name.lowercase().replace /s$/, ''
|
24
|
+
route += '('
|
25
|
+
route += Object.keys( controller.routedActions ).join '|'
|
26
|
+
route += ')?'
|
27
|
+
@routes[ route ] = controller
|
28
|
+
@
|
29
|
+
|
30
|
+
go: ( url = window.location.pathname, options = {} ) ->
|
31
|
+
url = @prepare( url ) or @prepare( @Application.defaultUrl )
|
32
|
+
if found = @findRoute url
|
33
|
+
{ controller, action, filters, params } = found
|
34
|
+
params[ name ] = value for name, value in options
|
35
|
+
controller.runAction action, filters, params
|
36
|
+
else if @Application.notFoundUrl
|
37
|
+
@go @Application.notFoundUrl
|
38
|
+
else console.warn "Not exists route to the address %s", url
|
39
|
+
@
|
40
|
+
|
41
|
+
prepare: ( url ) ->
|
42
|
+
url = url.replace "http://#{ window.location.host }", ''
|
43
|
+
url = url[ 1.. ] or '' if url and url[ 0...1 ] is '/'
|
44
|
+
url = url[ ...-1 ] or '' if url and url[ -1.. ] is '/'
|
45
|
+
url
|
46
|
+
|
47
|
+
findRoute: ( url ) ->
|
48
|
+
for route, controller of @routes when match = url.match new RegExp route, 'i'
|
49
|
+
segments = url.split( '/' )[ 1... ]
|
50
|
+
if segments[0] in Object.keys( controller.routedActions )
|
51
|
+
action = segments.shift()
|
52
|
+
else unless action = controller.actions.default
|
53
|
+
console.error 'Unspecified controller action'
|
54
|
+
filters = {}
|
55
|
+
for name in controller.routedActions[ action ].filters when segments[0]?
|
56
|
+
filters[ name ] = segments.shift()
|
57
|
+
params = {}
|
58
|
+
for name in controller.routedActions[ action ].params when segments[0]?
|
59
|
+
params[ name ] = segments.shift()
|
60
|
+
return controller: controller, action: action, filters: filters, params: params
|
61
|
+
false
|
62
|
+
|
63
|
+
saveHistory: ( value ) ->
|
64
|
+
@saveHistorySwitcher ?= true
|
65
|
+
if value in [ true, false ]
|
66
|
+
@saveHistorySwitcher = value
|
67
|
+
@
|
68
|
+
else @saveHistorySwitcher
|
69
|
+
|
70
|
+
setUrl: ( url ) ->
|
71
|
+
if @saveHistory()
|
72
|
+
history.pushState null, null, '/' + ( @url = url ) if url isnt @url
|
73
|
+
else @saveHistory true
|
74
|
+
@
|
@@ -0,0 +1,286 @@
|
|
1
|
+
Nali.extend View:
|
2
|
+
|
3
|
+
extension: ->
|
4
|
+
if @sysname isnt 'View'
|
5
|
+
@parseTemplate()
|
6
|
+
@parseEvents()
|
7
|
+
@
|
8
|
+
|
9
|
+
cloning: ->
|
10
|
+
@my = @model
|
11
|
+
@
|
12
|
+
|
13
|
+
layout: -> null
|
14
|
+
|
15
|
+
onSourceUpdated: -> @draw()
|
16
|
+
|
17
|
+
onSourceDestroyed: -> @hide()
|
18
|
+
|
19
|
+
getOf: ( source, property ) ->
|
20
|
+
@subscribeTo source, "update.#{ property }", @onSourceUpdated
|
21
|
+
source[ property ]
|
22
|
+
|
23
|
+
insertTo: ->
|
24
|
+
if ( layout = @layout() )?.childOf? 'View' then layout.show().element.find '.yield'
|
25
|
+
else @Application.htmlContainer
|
26
|
+
|
27
|
+
draw: ->
|
28
|
+
assistant.call @ for assistant in @assistants
|
29
|
+
@onDraw?()
|
30
|
+
@
|
31
|
+
|
32
|
+
show: ( insertTo = @insertTo() ) ->
|
33
|
+
@prepareElement().draw().bindEvents()
|
34
|
+
unless @visible
|
35
|
+
@model.beforeShow?[ @sysname ]?.call @model
|
36
|
+
@subscribeTo @model, 'update', @onSourceUpdated
|
37
|
+
@subscribeTo @model, 'destroy', @onSourceDestroyed
|
38
|
+
@element.appendTo insertTo
|
39
|
+
@showRelations()
|
40
|
+
setTimeout ( => @onShow() ), 5 if @onShow?
|
41
|
+
@visible = true
|
42
|
+
@model.afterShow?[ @sysname ]?.call @model
|
43
|
+
@
|
44
|
+
|
45
|
+
hide: ( delay = 0 ) ->
|
46
|
+
if @visible
|
47
|
+
@model.beforeHide?[ @sysname ]?.call @model
|
48
|
+
@hideDelay = delay if typeof( delay ) is 'number' and delay
|
49
|
+
@onHide?()
|
50
|
+
@trigger 'hide'
|
51
|
+
@hideElement()
|
52
|
+
@destroyObservation()
|
53
|
+
@visible = false
|
54
|
+
@model.afterHide?[ @sysname ]?.call @model
|
55
|
+
@
|
56
|
+
|
57
|
+
hideElement: ->
|
58
|
+
if @hideDelay? then setTimeout ( => @removeElement() ), @hideDelay else @removeElement()
|
59
|
+
@
|
60
|
+
|
61
|
+
removeElement: ->
|
62
|
+
@element[0].parentNode.removeChild @element[0]
|
63
|
+
@
|
64
|
+
|
65
|
+
showRelations: ->
|
66
|
+
for { selector, name, view } in @relationsMap
|
67
|
+
if ( relation = @model[ name ] )?
|
68
|
+
insertTo = @element.find selector
|
69
|
+
if relation.childOf 'Collection'
|
70
|
+
relation.show view, insertTo, true
|
71
|
+
relation.subscribeTo @, 'hide', relation.reset
|
72
|
+
else
|
73
|
+
view = relation.show view, insertTo
|
74
|
+
view.subscribeTo @, 'hide', view.hide
|
75
|
+
else console.warn "Relation %s does not exist of model %O", name, @model
|
76
|
+
@
|
77
|
+
|
78
|
+
runLink: ( event ) ->
|
79
|
+
event.preventDefault()
|
80
|
+
@runUrl event.currentTarget.getAttribute 'href'
|
81
|
+
@
|
82
|
+
|
83
|
+
runForm: ( event ) ->
|
84
|
+
event.preventDefault()
|
85
|
+
@runUrl event.currentTarget.getAttribute( 'action' ), @formToHash event.currentTarget
|
86
|
+
@
|
87
|
+
|
88
|
+
runUrl: ( url, params = {} ) ->
|
89
|
+
if match = url.match /^(@@?)(.+)/
|
90
|
+
[ method, data ] = match[2].split '?'
|
91
|
+
if data
|
92
|
+
for specification in data.split /&|&/ when specification
|
93
|
+
[ name, value ] = specification.split '='
|
94
|
+
params[ name ] = value
|
95
|
+
obj = if match[1].length is 1 then @ else @model
|
96
|
+
if obj[ method ]? and typeof obj[ method ] is 'function' then obj[ method ] params
|
97
|
+
else console.warn "Method %s not exists", method
|
98
|
+
else @Router.go url, params
|
99
|
+
@
|
100
|
+
|
101
|
+
formToHash: ( form ) ->
|
102
|
+
params = {}
|
103
|
+
for element in form.elements
|
104
|
+
if name = element.name or element.id
|
105
|
+
property = ( keys = name.match /[^\[\]]+/g ).pop()
|
106
|
+
target = params
|
107
|
+
for key in keys
|
108
|
+
target = if target[ key ] instanceof Object then target[ key ] else target[ key ] = {}
|
109
|
+
target[ property ] = element.value
|
110
|
+
params
|
111
|
+
|
112
|
+
parseEvents: ->
|
113
|
+
@eventsMap = []
|
114
|
+
if @events
|
115
|
+
@events = [ @events ] if typeof @events is 'string'
|
116
|
+
for event in @events
|
117
|
+
try
|
118
|
+
[ handlers, type, other ] = event.split /\s+(on|one)\s+/
|
119
|
+
[ events, selector ] = other.split /\s+at\s+/
|
120
|
+
handlers = handlers.split /\s*,\s*/
|
121
|
+
events = events.replace /\s*,\s*/, ' '
|
122
|
+
throw true unless type and events.length and handlers.length
|
123
|
+
catch
|
124
|
+
console.warn "Events parsing error: \"%s\" of %O", event, @
|
125
|
+
error = true
|
126
|
+
if error then error = false else @eventsMap.push [ selector, type, events, handlers ]
|
127
|
+
@
|
128
|
+
|
129
|
+
bindEvents: ->
|
130
|
+
unless @binded?
|
131
|
+
@element.find( 'a' ).on 'click', ( event ) => @runLink event
|
132
|
+
@element.find( 'form' ).on 'submit', ( event ) => @runForm event
|
133
|
+
@element.on 'click', ( event ) => @runLink event if @element.is 'a'
|
134
|
+
@element.on 'submit', ( event ) => @runForm event if @element.is 'form'
|
135
|
+
for [ selector, type, events, handlers ] in @eventsMap
|
136
|
+
for handler in handlers
|
137
|
+
do ( selector, type, events, handler ) =>
|
138
|
+
@element[ type ] events, selector, ( event ) => @[ handler ] event
|
139
|
+
@binded = true
|
140
|
+
@
|
141
|
+
|
142
|
+
prepareElement: ->
|
143
|
+
unless @element
|
144
|
+
@element = @_ @template
|
145
|
+
@element[0].view = @
|
146
|
+
@addAssistants()
|
147
|
+
@
|
148
|
+
|
149
|
+
getNode: ( path ) ->
|
150
|
+
node = @element[0]
|
151
|
+
node = node[ sub ] for sub in path
|
152
|
+
node
|
153
|
+
|
154
|
+
parseTemplate: ->
|
155
|
+
if container = document.querySelector '#' + @sysname.underscore()
|
156
|
+
@template = container.innerHTML.trim().replace( /\s+/g, ' ' )
|
157
|
+
.replace( /({\s*\+.+?\s*})/g, ' <assist>$1</assist>' )
|
158
|
+
.replace( /{\s*yield\s*}/g, '<div class="yield"></div>' )
|
159
|
+
unless RegExp( "^<[^>]+" + @sysname ).test @template
|
160
|
+
@template = "<div class=\"#{ @sysname }\">#{ @template }</div>"
|
161
|
+
@parseRelations()
|
162
|
+
container.parentNode.removeChild container
|
163
|
+
else console.warn 'Template %s not exists', @sysname
|
164
|
+
@
|
165
|
+
|
166
|
+
parseRelations: ->
|
167
|
+
@relationsMap = []
|
168
|
+
@template = @template.replace /{\s*(\w+) of @(\w+)\s*}/g, ( match, view, relation ) =>
|
169
|
+
className = relation.capitalize() + view.capitalize() + 'Relation'
|
170
|
+
@relationsMap.push selector: '.' + className, name: relation, view: view
|
171
|
+
"<div class=\"#{ className }\"></div>"
|
172
|
+
@parseAssistants()
|
173
|
+
@
|
174
|
+
|
175
|
+
parseAssistants: ->
|
176
|
+
@assistantsMap = []
|
177
|
+
if /{\s*.+?\s*}|bind=".+?"/.test @template
|
178
|
+
tmp = document.createElement 'div'
|
179
|
+
tmp.innerHTML = @template
|
180
|
+
@scanAssistants tmp.children[0]
|
181
|
+
@
|
182
|
+
|
183
|
+
scanAssistants: ( node, path = [] ) ->
|
184
|
+
if node.nodeType is 3 and /{\s*.+?\s*}/.test node.textContent
|
185
|
+
@assistantsMap.push nodepath: path, type: 'Text'
|
186
|
+
else if node.nodeName is 'ASSIST'
|
187
|
+
@assistantsMap.push nodepath: path, type: 'Html'
|
188
|
+
else
|
189
|
+
if node.attributes
|
190
|
+
for attribute, index in node.attributes
|
191
|
+
if attribute.name is 'bind'
|
192
|
+
@assistantsMap.push nodepath: path, type: 'Form'
|
193
|
+
else if /{\s*.+?\s*}/.test attribute.textContent
|
194
|
+
@assistantsMap.push nodepath: path.concat( 'attributes', index ), type: 'Text'
|
195
|
+
@scanAssistants child, path.concat 'childNodes', index for child, index in node.childNodes
|
196
|
+
@
|
197
|
+
|
198
|
+
addAssistants: ->
|
199
|
+
@assistants = []
|
200
|
+
@[ "add#{ type }Assistant" ] @getNode nodepath for { nodepath, type } in @assistantsMap
|
201
|
+
@
|
202
|
+
|
203
|
+
addTextAssistant: ( node ) ->
|
204
|
+
initialValue = node.textContent
|
205
|
+
@assistants.push -> node.textContent = @analize initialValue
|
206
|
+
@
|
207
|
+
|
208
|
+
addHtmlAssistant: ( node ) ->
|
209
|
+
parent = node.parentNode
|
210
|
+
initialValue = node.innerHTML
|
211
|
+
index = Array::indexOf.call parent.childNodes, node
|
212
|
+
after = parent.childNodes[ index - 1 ] or null
|
213
|
+
before = parent.childNodes[ index + 1 ] or null
|
214
|
+
@assistants.push ->
|
215
|
+
start = if after then Array::indexOf.call( parent.childNodes, after ) + 1 else 0
|
216
|
+
end = if before then Array::indexOf.call parent.childNodes, before else parent.childNodes.length
|
217
|
+
parent.removeChild node for node in Array::slice.call( parent.childNodes, start, end )
|
218
|
+
parent.insertBefore element, before for element in @_( @analize initialValue )
|
219
|
+
@
|
220
|
+
|
221
|
+
addFormAssistant: ( node ) ->
|
222
|
+
if bind = @analizeChain node.attributes.removeNamedItem( 'bind' ).value
|
223
|
+
[ source, property ] = bind
|
224
|
+
if node.type in [ 'text', 'textarea']
|
225
|
+
node.value = source[ property ]
|
226
|
+
|
227
|
+
@_( node ).on 'change', =>
|
228
|
+
( params = {} )[ property ] = node.value
|
229
|
+
source.update params
|
230
|
+
source.save() unless node.form?
|
231
|
+
|
232
|
+
source.subscribe @, "update.#{ property }", =>
|
233
|
+
node.value = source[ property ] if node.value isnt source[ property ]
|
234
|
+
|
235
|
+
if node.type in [ 'checkbox', 'radio' ]
|
236
|
+
node.checked = source[ property ] + '' is node.value
|
237
|
+
|
238
|
+
@_( node ).on 'change', =>
|
239
|
+
if node.checked is true
|
240
|
+
( params = {} )[ property ] = node.value
|
241
|
+
source.update params
|
242
|
+
source.save() unless node.form?
|
243
|
+
|
244
|
+
source.subscribe @, "update.#{ property }", =>
|
245
|
+
node.checked = source[ property ] + '' is node.value
|
246
|
+
|
247
|
+
if node.type is 'select-one'
|
248
|
+
option.selected = true for option in node when source[ property ] + '' is option.value
|
249
|
+
|
250
|
+
@_( node ).on 'change', =>
|
251
|
+
( params = {} )[ property ] = node.value
|
252
|
+
source.update params
|
253
|
+
source.save() unless node.form?
|
254
|
+
|
255
|
+
source.subscribe @, "update.#{ property }", =>
|
256
|
+
option.selected = true for option in node when source[ property ] + '' is option.value
|
257
|
+
|
258
|
+
|
259
|
+
|
260
|
+
analize: ( value ) ->
|
261
|
+
value.replace /{\s*(.+?)\s*}/g, ( match, sub ) => @analizeMatch sub
|
262
|
+
|
263
|
+
analizeMatch: ( sub ) ->
|
264
|
+
if match = sub.match /^@([\w\.]+)(\?)?$/
|
265
|
+
if result = @analizeChain match[1]
|
266
|
+
[ source, property ] = result
|
267
|
+
source.subscribe? @, "update.#{ property }", @onSourceUpdated if source isnt @model
|
268
|
+
if match[2] is '?'
|
269
|
+
if source[ property ] then property else ''
|
270
|
+
else source[ property ]
|
271
|
+
else ''
|
272
|
+
else if match = sub.match /^[=|\+](\w+)$/
|
273
|
+
@helpers?[ match[1] ]?.call @
|
274
|
+
else undefined
|
275
|
+
|
276
|
+
analizeChain: ( chain ) ->
|
277
|
+
segments = chain.split '.'
|
278
|
+
property = segments.pop()
|
279
|
+
source = @model
|
280
|
+
for segment in segments
|
281
|
+
if segment of source then source = source[ segment ]
|
282
|
+
else break
|
283
|
+
unless property of source
|
284
|
+
console.warn "%s: chain \"%s\" is invalid, \"%s\" is not Object", @sysname, chain, segment
|
285
|
+
return null
|
286
|
+
[ source, property ]
|