nali 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 ]