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.
@@ -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 ]