joosy 1.2.0.beta.4 → 1.2.0.rc.1

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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.codoopts +1 -1
  3. data/Gruntfile.coffee +3 -3
  4. data/README.md +4 -0
  5. data/bower.json +1 -1
  6. data/build/joosy.js +2 -2
  7. data/build/joosy/form.js +1 -1
  8. data/build/joosy/resources.js +1 -1
  9. data/package.json +2 -2
  10. data/source/joosy/application.coffee +2 -2
  11. data/source/joosy/form.coffee +4 -4
  12. data/source/joosy/helpers/form.coffee +12 -3
  13. data/source/joosy/helpers/index.coffee +0 -1
  14. data/source/joosy/helpers/view.coffee +16 -3
  15. data/source/joosy/layout.coffee +0 -4
  16. data/source/joosy/module.coffee +16 -1
  17. data/source/joosy/modules/dom.coffee +106 -101
  18. data/source/joosy/modules/events.coffee +44 -10
  19. data/source/joosy/modules/filters.coffee +64 -60
  20. data/source/joosy/modules/page.coffee +3 -0
  21. data/source/joosy/modules/page/scrolling.coffee +46 -29
  22. data/source/joosy/modules/page/title.coffee +14 -0
  23. data/source/joosy/modules/renderer.coffee +219 -190
  24. data/source/joosy/modules/resources.coffee +3 -0
  25. data/source/joosy/modules/resources/cacher.coffee +81 -10
  26. data/source/joosy/modules/resources/function.coffee +26 -29
  27. data/source/joosy/modules/resources/identity_map.coffee +64 -42
  28. data/source/joosy/modules/resources/model.coffee +127 -73
  29. data/source/joosy/modules/time_manager.coffee +2 -0
  30. data/source/joosy/page.coffee +3 -6
  31. data/source/joosy/resources/array.coffee +87 -2
  32. data/source/joosy/resources/hash.coffee +53 -1
  33. data/source/joosy/resources/rest.coffee +59 -3
  34. data/source/joosy/resources/scalar.coffee +47 -1
  35. data/source/joosy/router.coffee +63 -21
  36. data/source/joosy/templaters/jst.coffee +3 -0
  37. data/source/joosy/widget.coffee +17 -11
  38. data/spec/joosy/core/helpers/view_spec.coffee +14 -0
  39. data/spec/joosy/core/modules/dom_spec.coffee +1 -1
  40. data/spec/joosy/core/modules/filters_spec.coffee +2 -2
  41. data/spec/joosy/core/modules/module_spec.coffee +1 -1
  42. data/spec/joosy/core/modules/renderer_spec.coffee +19 -1
  43. data/spec/joosy/core/router_spec.coffee +80 -45
  44. data/spec/joosy/core/widget_spec.coffee +9 -0
  45. data/spec/joosy/resources/modules/cacher_spec.coffee +3 -3
  46. data/spec/joosy/resources/modules/function_spec.coffee +2 -2
  47. data/spec/joosy/resources/modules/identity_map_spec.coffee +2 -2
  48. data/spec/joosy/resources/modules/model_spec.coffee +1 -1
  49. metadata +2 -5
  50. data/source/joosy/helpers/routes.coffee +0 -17
  51. data/source/joosy/modules/widgets_manager.coffee +0 -90
  52. data/spec/joosy/core/helpers/routes_spec.coffee +0 -15
@@ -1,13 +1,14 @@
1
1
  #= require joosy/joosy
2
2
 
3
- # @private
3
+ # @nodoc
4
4
  class SynchronizationContext
5
5
  constructor: -> @actions = []
6
6
  do: (action) -> @actions.push action
7
7
  after: (@after) ->
8
8
 
9
9
  #
10
- # @private
10
+ # @nodoc
11
+ #
11
12
  # Events namespace
12
13
  #
13
14
  # Creates unified collection of bindings to a particular instance
@@ -46,12 +47,18 @@ Joosy.Modules.Events =
46
47
  # Creates events namespace
47
48
  #
48
49
  # @example
49
- # namespace = @entity.eventsNamespace, ->
50
+ # namespace = @entity.eventsNamespace ->
50
51
  # @bind 'action1', ->
51
52
  # @bind 'action2', ->
52
53
  #
53
54
  # namespace.unbind()
54
55
  #
56
+ # @example
57
+ # namespace = @entity.eventsNamespace()
58
+ # namespace.bind 'action1', ->
59
+ # namespace.bind 'action2', ->
60
+ # namespace.unbind()
61
+ #
55
62
  eventsNamespace: (actions) ->
56
63
  namespace = new Namespace @
57
64
  actions?.call?(namespace)
@@ -60,10 +67,20 @@ Joosy.Modules.Events =
60
67
  #
61
68
  # Waits for the list of given events to happen at least once. Then runs callback.
62
69
  #
63
- # @param [String|Array] events List of events to wait for separated by space
70
+ # @overload ~wait(events, callback)
71
+ # Uses internal unique ID as the name of the binding
72
+ #
73
+ # @overload ~wait(name, events, callback)
74
+ # Allows to pass custom name for the binding
75
+ #
76
+ # @param [String] name Custom name for the binding
77
+ # @param [String] events List of events to wait for separated by space
78
+ # @param [Array] events List of events to wait in the form of Array
64
79
  # @param [Function] callback Action to run when all events were triggered at least once
65
80
  # @param [Hash] options Options
66
81
  #
82
+ # @return [String] An ID (or custom name) of binding
83
+ #
67
84
  wait: (name, events, callback) ->
68
85
  @__oneShotEvents = {} unless @hasOwnProperty('__oneShotEvents')
69
86
 
@@ -85,7 +102,7 @@ Joosy.Modules.Events =
85
102
  #
86
103
  # Removes waiter action
87
104
  #
88
- # @param [Function] target Name of waiter to unbind
105
+ # @param [String] target Name of {Joosy.Modules.Events~wait} binding
89
106
  #
90
107
  unwait: (target) ->
91
108
  delete @__oneShotEvents[target] if @hasOwnProperty '__oneShotEvents'
@@ -93,9 +110,18 @@ Joosy.Modules.Events =
93
110
  #
94
111
  # Binds action to run each time any of given event was triggered
95
112
  #
96
- # @param [String|Array] events List of events separated by space
113
+ # @overload ~bind(events, callback)
114
+ # Uses internal unique ID as the name of the binding
115
+ #
116
+ # @overload ~bind(name, events, callback)
117
+ # Allows to pass custom name for the binding
118
+ #
119
+ # @param [String] name Custom name for the binding
120
+ # @param [String] events List of events to wait for separated by space
121
+ # @param [Array] events List of events to wait in the form of Array
97
122
  # @param [Function] callback Action to run on trigger
98
- # @param [Hash] options Options
123
+ #
124
+ # @return [String] An ID (or custom name) of binding
99
125
  #
100
126
  bind: (name, events, callback) ->
101
127
  @__boundEvents = {} unless @hasOwnProperty '__boundEvents'
@@ -118,15 +144,16 @@ Joosy.Modules.Events =
118
144
  #
119
145
  # Unbinds action from runing on trigger
120
146
  #
121
- # @param [Function] target Name of bind to unbind
147
+ # @param [String] target Name of {Joosy.Modules.Events~bind} binding
122
148
  #
123
149
  unbind: (target) ->
124
150
  delete @__boundEvents[target] if @hasOwnProperty '__boundEvents'
125
151
 
126
152
  #
127
- # Triggers event for {bind} and {wait}
153
+ # Triggers event for {Joosy.Modules.Events~bind} and {Joosy.Modules.Events~wait}
128
154
  #
129
- # @param [String] Name of event to trigger
155
+ # @param [String] event Name of event to trigger
156
+ # @param [Mixed] data Data to pass to event
130
157
  #
131
158
  trigger: (event, data...) ->
132
159
  Joosy.Modules.Log.debugAs @, "Event #{event} triggered"
@@ -187,6 +214,13 @@ Joosy.Modules.Events =
187
214
  if ++counter >= context.actions.length
188
215
  context.after.call(@)
189
216
 
217
+ #
218
+ # Turns the list of events given in form of stiring into the array
219
+ #
220
+ # @param [String] events
221
+ # @return [Array]
222
+ # @private
223
+ #
190
224
  __splitEvents: (events) ->
191
225
  if typeof(events) == 'string'
192
226
  if events.length == 0
@@ -4,82 +4,86 @@
4
4
  # Filters registration routines
5
5
  #
6
6
  # @mixin
7
+ # @private
7
8
  #
8
9
  Joosy.Modules.Filters =
9
10
 
10
11
  #
11
- # Defines static registration routines
12
+ # Internal helper registering filters accessors
12
13
  #
13
- # @example Set of methods
14
- # class Test
15
- # @beforeLoad -> # supposed to run before load and control loading queue
16
- # @afterLoad -> # supposed to run after load to finalize loading
17
- # @afterUnload -> # supposed to run after unload to collect garbage
14
+ __registerFilterCollector: (filter) ->
15
+ @[filter] = (callback) ->
16
+ unless @::hasOwnProperty "__#{filter}s"
17
+ @::["__#{filter}s"] = [].concat @.__super__["__#{filter}s"] || []
18
+ @::["__#{filter}s"].push callback
19
+
20
+ filter.charAt(0).toUpperCase() + filter.slice(1)
21
+
18
22
  #
19
- # # private
23
+ # Registers a set of plain (synchronous) filters
20
24
  #
21
- # @__confirmBeforeLoads() # Runs filters registered as beforeLoad
22
- # @__runAfterLoads() # Runs filters registered as afterLoad
23
- # @__runAfterUnloads() # Runs filters registered as afterUnload
25
+ # @example
26
+ # class Test
27
+ # @extend Joosy.Modules.Filters
28
+ # @registerPlainFilters 'beforeLoad', 'afterLoad'
24
29
  #
25
- included: ->
26
- @__registerFilterCollector = (filter) ->
27
- @[filter] = (callback) ->
28
- unless @::hasOwnProperty "__#{filter}s"
29
- @::["__#{filter}s"] = [].concat @.__super__["__#{filter}s"] || []
30
- @::["__#{filter}s"].push callback
31
-
32
- filter.charAt(0).toUpperCase() + filter.slice(1)
33
-
34
- @registerPlainFilters = (filters...) ->
35
- for filter in filters
36
- do (filter) =>
37
- camelized = @__registerFilterCollector filter
38
-
39
- @::["__run#{camelized}s"] = (params...) ->
40
- return unless @["__#{filter}s"]
30
+ registerPlainFilters: (filters...) ->
31
+ for filter in filters
32
+ do (filter) =>
33
+ camelized = @__registerFilterCollector filter
41
34
 
42
- for callback in @["__#{filter}s"]
43
- callback = @[callback] unless typeof(callback) == 'function'
44
- callback.apply(@, params)
35
+ @::["__run#{camelized}s"] = (params...) ->
36
+ return unless @["__#{filter}s"]
45
37
 
46
- @::["__confirm#{camelized}s"] = (params...) ->
47
- return true unless @["__#{filter}s"]
38
+ for callback in @["__#{filter}s"]
39
+ callback = @[callback] unless typeof(callback) == 'function'
40
+ callback.apply(@, params)
48
41
 
49
- @["__#{filter}s"].reduce (flag, callback) =>
50
- callback = @[callback] unless typeof(callback) == 'function'
51
- flag && callback.apply(@, params) != false
52
- , true
42
+ @::["__confirm#{camelized}s"] = (params...) ->
43
+ return true unless @["__#{filter}s"]
53
44
 
54
- @::["__apply#{camelized}s"] = (data, params...) ->
55
- return data unless @["__#{filter}s"]
45
+ @["__#{filter}s"].reduce (flag, callback) =>
46
+ callback = @[callback] unless typeof(callback) == 'function'
47
+ flag && callback.apply(@, params) != false
48
+ , true
56
49
 
57
- for callback in @["__#{filter}s"]
58
- callback = @[callback] unless typeof(callback) == 'function'
59
- data = callback.apply(@, [data].concat params)
50
+ @::["__apply#{camelized}s"] = (data, params...) ->
51
+ return data unless @["__#{filter}s"]
60
52
 
61
- data
53
+ for callback in @["__#{filter}s"]
54
+ callback = @[callback] unless typeof(callback) == 'function'
55
+ data = callback.apply(@, [data].concat params)
62
56
 
63
- @registerSequencedFilters = (filters...) ->
64
- for filter in filters
65
- do (filter) =>
66
- camelized = @__registerFilterCollector filter
57
+ data
67
58
 
68
- @::["__run#{camelized}s"] = (params, callback) ->
69
- return callback() unless @["__#{filter}s"]
70
-
71
- runners = @["__#{filter}s"]
72
- filterer = @
73
-
74
- if runners.length == 1
75
- return runners[0].apply @, params.concat(callback)
76
-
77
- Joosy.synchronize (context) ->
78
- for runner in runners
79
- do (runner) ->
80
- context.do (done) ->
81
- runner.apply filterer, params.concat(done)
82
- context.after callback
59
+ #
60
+ # Registers a set of sequenced (asynchronous) filters
61
+ #
62
+ # @example
63
+ # class Test
64
+ # @extend Joosy.Modules.Filters
65
+ # @registerSequencedFilters 'fetch', 'paint'
66
+ #
67
+ registerSequencedFilters: (filters...) ->
68
+ for filter in filters
69
+ do (filter) =>
70
+ camelized = @__registerFilterCollector filter
71
+
72
+ @::["__run#{camelized}s"] = (params, callback) ->
73
+ return callback() unless @["__#{filter}s"]
74
+
75
+ runners = @["__#{filter}s"]
76
+ filterer = @
77
+
78
+ if runners.length == 1
79
+ return runners[0].apply @, params.concat(callback)
80
+
81
+ Joosy.synchronize (context) ->
82
+ for runner in runners
83
+ do (runner) ->
84
+ context.do (done) ->
85
+ runner.apply filterer, params.concat(done)
86
+ context.after callback
83
87
 
84
88
  # AMD wrapper
85
89
  if define?.amd?
@@ -1 +1,4 @@
1
+ #
2
+ # The namespace for page modules
3
+ #
1
4
  Joosy.Modules.Page = {}
@@ -1,9 +1,22 @@
1
1
  #= require ../page
2
2
 
3
+ #
4
+ # The auto-scrolling filters for Page (or possibly widgets)
5
+ #
6
+ # @see Joosy.Page
3
7
  # @mixin
8
+ #
4
9
  Joosy.Modules.Page.Scrolling =
5
10
 
6
11
  included: ->
12
+ @afterLoad ->
13
+ @__performScrolling() if @__scrollElement
14
+
15
+ @paint (complete) ->
16
+ @__fixHeight() if @__scrollElement && @__scrollSpeed != 0
17
+ complete()
18
+
19
+ ClassMethods:
7
20
  #
8
21
  # Sets the position where page will be scrolled to after load.
9
22
  #
@@ -17,38 +30,42 @@ Joosy.Modules.Page.Scrolling =
17
30
  # @option options [Integer] margin Defines the margin from element position.
18
31
  # Can be negative.
19
32
  #
20
- @scroll = (element, options={}) ->
33
+ # @example
34
+ # class TestPage extends Joosy.Page
35
+ # @scroll '#header', speed: 300, margin: -100
36
+ #
37
+ scroll: (element, options={}) ->
21
38
  @::__scrollElement = element
22
39
  @::__scrollSpeed = options.speed || 500
23
40
  @::__scrollMargin = options.margin || 0
24
41
 
25
- @paint (complete) ->
26
- @__fixHeight() if @__scrollElement && @__scrollSpeed != 0
27
- complete()
28
-
29
- @afterLoad ->
30
- @__performScrolling() if @__scrollElement
31
-
32
- #
33
- # Scrolls page to stored positions
34
- #
35
- __performScrolling: ->
36
- scroll = $(@__extractSelector @__scrollElement).offset()?.top + @__scrollMargin
37
- Joosy.Modules.Log.debugAs @, "Scrolling to #{@__extractSelector @__scrollElement}"
38
- $('html, body').animate {scrollTop: scroll}, @__scrollSpeed, =>
39
- if @__scrollSpeed != 0
40
- @__releaseHeight()
42
+ InstanceMethods:
43
+ #
44
+ # Scrolls page to stored positions
45
+ #
46
+ # @private
47
+ #
48
+ __performScrolling: ->
49
+ scroll = $(@__extractSelector @__scrollElement).offset()?.top + @__scrollMargin
50
+ Joosy.Modules.Log.debugAs @, "Scrolling to #{@__extractSelector @__scrollElement}"
51
+ $('html, body').animate {scrollTop: scroll}, @__scrollSpeed, =>
52
+ if @__scrollSpeed != 0
53
+ @__releaseHeight()
41
54
 
42
- #
43
- # Freezes the page height through $(html).
44
- #
45
- # Required to implement better {Joosy.Page.scroll} behavior.
46
- #
47
- __fixHeight: ->
48
- $('html').css 'min-height', $(document).height()
55
+ #
56
+ # Freezes the page height through $(html).
57
+ #
58
+ # Required to implement better {Joosy.Modules.Page.Scrolling.scroll} behavior.
59
+ #
60
+ # @private
61
+ #
62
+ __fixHeight: ->
63
+ $('html').css 'min-height', $(document).height()
49
64
 
50
- #
51
- # Undo {#__fixHeight}
52
- #
53
- __releaseHeight: ->
54
- $('html').css 'min-height', ''
65
+ #
66
+ # Undoes {Joosy.Modules.Page.Scrolling#__fixHeight}
67
+ #
68
+ # @private
69
+ #
70
+ __releaseHeight: ->
71
+ $('html').css 'min-height', ''
@@ -1,6 +1,11 @@
1
1
  #= require ../page
2
2
 
3
+ #
4
+ # Title management for Page (or possibly other widgets)
5
+ #
6
+ # @see Joosy.Page
3
7
  # @mixin
8
+ #
4
9
  Joosy.Modules.Page.Title =
5
10
 
6
11
  #
@@ -9,6 +14,15 @@ Joosy.Modules.Page.Title =
9
14
  # @note Title will be reverted on unload.
10
15
  #
11
16
  # @param [String] title Title to set.
17
+ # @param [String] separator The string to use to `.join` when title is an array
18
+ #
19
+ # @example
20
+ # class TestPage extends Joosy.Page
21
+ # @title 'Test title'
22
+ #
23
+ # @example
24
+ # class TestPage extends Joosy.Page
25
+ # @title -> I18n.t('titles.test')
12
26
  #
13
27
  title: (title, separator=' / ') ->
14
28
  @afterLoad ->
@@ -7,14 +7,16 @@
7
7
  # @mixin
8
8
  #
9
9
  Joosy.Modules.Renderer =
10
- #
11
- # Defines class-level helpers: @view and @helpers
12
- #
13
- # View (@view): Sets the curent template by specifying its name or lambda
14
- # Helpers (@helpers): Lists set of helpers' namespaces to include
15
- #
16
- included: ->
17
- @view = (template, options={}) ->
10
+
11
+ ClassMethods:
12
+ #
13
+ # Sets the curent template by specifying its name or lambda
14
+ #
15
+ # @param [String] template
16
+ # @param [Hash] options
17
+ # @option options [Boolean] dynamic Marks if the whole view should be rendered as a Dynamic one
18
+ #
19
+ view: (template, options={}) ->
18
20
  @::__view = template
19
21
  @::__renderDefault = (locals={}) ->
20
22
  if options.dynamic
@@ -22,195 +24,222 @@ Joosy.Modules.Renderer =
22
24
  else
23
25
  @render template, locals
24
26
 
25
- @helper = (helpers...) ->
27
+ #
28
+ # Lists set of helpers' namespaces to include
29
+ #
30
+ helper: (helpers...) ->
26
31
  unless @::hasOwnProperty "__helpers"
27
32
  @::__helpers = @.__super__.__helpers?.slice() || []
28
33
 
29
34
  @::__helpers = @::__helpers.concat(helpers).filter (value, i, array) ->
30
35
  array.indexOf(value) == i
31
36
 
32
- #
33
- # Renders given template with given locals
34
- #
35
- # @param [String] template Name of the template to render using templater
36
- # @param [Function] template `(locals) ->` lambda to use as template
37
- # @param [Object] locals Locals to assign
38
- # @param [Object] parentStackPointer Internal rendering stack pointer
39
- #
40
- render: (template, locals={}, parentStackPointer=false) ->
41
- @__render false, template, locals, parentStackPointer
42
-
43
- #
44
- # Dynamically renders given template with given locals
45
- #
46
- # Whenever any of assigned locals triggers `changed` event, DOM will automatically be refreshed
47
- #
48
- # @param [String] template Name of the template to render using templater
49
- # @param [Function] template `(locals) ->` lambda to use as template
50
- # @param [Object] locals Locals to assign
51
- # @param [Object] parentStackPointer Internal rendering stack pointer
52
- #
53
- renderDynamic: (template, locals={}, parentStackPointer=false) ->
54
- @__render true, template, locals, parentStackPointer
55
-
56
- #
57
- # Converts all possible `@helper` arguments to the objects available for merge
58
- #
59
- __assignHelpers: ->
60
- return unless @__helpers?
61
-
62
- unless @hasOwnProperty "__helpers"
63
- @__helpers = @__helpers.slice()
64
-
65
- for helper, i in @__helpers
66
- do (helper, i) =>
67
- unless helper.constructor == Object
68
- unless @[helper]?
69
- throw new Error "Cannot find method '#{helper}' to use as helper"
70
-
71
- @__helpers[i] = {}
72
- @__helpers[i][helper] = => @[helper] arguments...
73
-
74
- #
75
- # Collects and merges all requested helpers including global scope to one cached object
76
- #
77
- __instantiateHelpers: ->
78
- unless @__helpersInstance
79
- @__assignHelpers()
80
-
81
- @__helpersInstance = {}
82
- @__helpersInstance.__renderer = @
83
-
84
- Joosy.Module.merge @__helpersInstance, Joosy.Helpers.Application
85
- Joosy.Module.merge @__helpersInstance, Joosy.Helpers.Routes if Joosy.Helpers.Routes?
86
-
87
- if @__helpers
88
- for helper in @__helpers
89
- Joosy.Module.merge @__helpersInstance, helper
90
-
91
- @__helpersInstance
92
-
93
- #
94
- # Defines local `@render*` methods with proper stack pointer set
95
- #
96
- # @param [Object] parentStackPointer Internal rendering stack pointer
97
- #
98
- __instantiateRenderers: (parentStackPointer) ->
99
- render: (template, locals={}) =>
100
- @render template, locals, parentStackPointer
101
- renderDynamic: (template, locals={}) =>
102
- @renderDynamic template, locals, parentStackPointer
103
- renderInline: (locals={}, partial) =>
104
- template = (params) -> partial.apply(params)
105
- @renderDynamic template, locals, parentStackPointer
106
-
107
- #
108
- # Actual rendering implementation
109
- #
110
- __render: (dynamic, template, locals={}, parentStackPointer=false) ->
111
- stack = @__renderingStackChildFor parentStackPointer
112
-
113
- stack.template = template
114
- stack.locals = locals
115
-
116
- if typeof(template) == 'string'
117
- if @__renderSection?
118
- template = Joosy.templater().resolveTemplate @__renderSection(), template, this
119
- template = Joosy.templater().buildView template
120
- else if typeof(template) != 'function'
121
- throw new Error "#{Joosy.Module.__className @}> template (maybe @view) does not look like a string or lambda"
122
-
123
- if locals.constructor != Object
124
- throw new Error "#{Joosy.Module.__className @}> locals (maybe @data?) is not a hash"
125
-
126
- context = =>
127
- data = {}
128
-
129
- Joosy.Module.merge data, stack.locals
130
- Joosy.Module.merge data, @__instantiateHelpers(), false
131
- Joosy.Module.merge data, @__instantiateRenderers(stack)
132
- data
133
-
134
- result = ->
135
- template(context())
136
-
137
- if dynamic
138
- morph = Metamorph result()
139
- update = =>
140
- if morph.isRemoved()
141
- for [object, binding] in morph.__bindings
142
- object.unbind binding
143
- else
144
- for child in stack.children
37
+ InstanceMethods:
38
+ #
39
+ # Renders given template with given locals
40
+ #
41
+ # @param [String] template Name of the template to render using templater
42
+ # @param [Function] template `(locals) ->` lambda to use as template
43
+ # @param [Object] locals Locals to assign
44
+ # @param [Object] parentStackPointer Internal rendering stack pointer
45
+ #
46
+ render: (template, locals={}, parentStackPointer=false) ->
47
+ @__render false, template, locals, parentStackPointer
48
+
49
+ #
50
+ # Dynamically renders given template with given locals
51
+ #
52
+ # Whenever any of assigned locals triggers `changed` event, DOM will automatically be refreshed
53
+ #
54
+ # @param [String] template Name of the template to render using templater
55
+ # @param [Function] template `(locals) ->` lambda to use as template
56
+ # @param [Object] locals Locals to assign
57
+ # @param [Object] parentStackPointer Internal rendering stack pointer
58
+ #
59
+ renderDynamic: (template, locals={}, callback, parentStackPointer=false) ->
60
+ @__render (callback || true), template, locals, parentStackPointer
61
+
62
+ #
63
+ # Converts all possible `@helper` arguments to the objects available for merge
64
+ #
65
+ # @private
66
+ #
67
+ __assignHelpers: ->
68
+ return unless @__helpers?
69
+
70
+ unless @hasOwnProperty "__helpers"
71
+ @__helpers = @__helpers.slice()
72
+
73
+ for helper, i in @__helpers
74
+ do (helper, i) =>
75
+ unless helper.constructor == Object
76
+ unless @[helper]?
77
+ throw new Error "Cannot find method '#{helper}' to use as helper"
78
+
79
+ @__helpers[i] = {}
80
+ @__helpers[i][helper] = => @[helper] arguments...
81
+
82
+ #
83
+ # Collects and merges all requested helpers including global scope to one cached object
84
+ #
85
+ # @private
86
+ #
87
+ __instantiateHelpers: ->
88
+ unless @__helpersInstance
89
+ @__assignHelpers()
90
+
91
+ @__helpersInstance = {}
92
+ @__helpersInstance.__renderer = @
93
+
94
+ Joosy.Module.merge @__helpersInstance, Joosy.Helpers.Application
95
+ Joosy.Module.merge @__helpersInstance, Joosy.Helpers.Routes if Joosy.Helpers.Routes?
96
+
97
+ if @__helpers
98
+ for helper in @__helpers
99
+ Joosy.Module.merge @__helpersInstance, helper
100
+
101
+ @__helpersInstance
102
+
103
+ #
104
+ # Defines local `@render*` methods with proper stack pointer set
105
+ #
106
+ # @param [Object] parentStackPointer Internal rendering stack pointer
107
+ # @private
108
+ #
109
+ __instantiateRenderers: (parentStackPointer) ->
110
+
111
+ render: (template, locals={}) =>
112
+ @render template, locals, parentStackPointer
113
+
114
+ renderDynamic: (template, locals={}, callback) =>
115
+ @renderDynamic template, locals, callback, parentStackPointer
116
+
117
+ renderInline: (locals={}, callback, partial) =>
118
+ if arguments.length < 3
119
+ partial = callback
120
+ callback = undefined
121
+
122
+ template = (params) ->
123
+ partial.apply(params)
124
+
125
+ @renderDynamic template, locals, callback, parentStackPointer
126
+
127
+ #
128
+ # Actual rendering implementation
129
+ #
130
+ # @private
131
+ #
132
+ __render: (dynamic, template, locals={}, parentStackPointer=false) ->
133
+ stack = @__renderingStackChildFor parentStackPointer
134
+
135
+ stack.template = template
136
+ stack.locals = locals
137
+
138
+ if typeof(template) == 'string'
139
+ if @__renderSection?
140
+ template = Joosy.templater().resolveTemplate @__renderSection(), template, this
141
+ template = Joosy.templater().buildView template
142
+ else if typeof(template) != 'function'
143
+ throw new Error "#{Joosy.Module.__className @}> template (maybe @view) does not look like a string or lambda"
144
+
145
+ if locals.constructor != Object
146
+ throw new Error "#{Joosy.Module.__className @}> locals (maybe @data?) is not a hash"
147
+
148
+ context = =>
149
+ data = {}
150
+
151
+ Joosy.Module.merge data, stack.locals
152
+ Joosy.Module.merge data, @__instantiateHelpers(), false
153
+ Joosy.Module.merge data, @__instantiateRenderers(stack)
154
+ data
155
+
156
+ result = ->
157
+ template(context())
158
+
159
+ if dynamic
160
+ morph = Metamorph result()
161
+ update = =>
162
+ if morph.isRemoved()
163
+ for [object, binding] in morph.__bindings
164
+ object.unbind binding
165
+ else
166
+ for child in stack.children
167
+ @__removeMetamorphs child
168
+ stack.children = []
169
+ morph.html result()
170
+ dynamic() if dynamic instanceof Function
171
+
172
+ # This is here to break stack tree and save from
173
+ # repeating DOM modification
174
+ timeout = null
175
+ debouncedUpdate = ->
176
+ clearTimeout timeout
177
+ timeout = setTimeout update, 0
178
+
179
+ for key, object of locals
180
+ if locals.hasOwnProperty key
181
+ if object?.bind? && object?.unbind?
182
+ binding = [object, object.bind('changed', debouncedUpdate)]
183
+ stack.metamorphBindings.push binding
184
+
185
+ morph.__bindings = stack.metamorphBindings
186
+
187
+ morph.outerHTML()
188
+ else
189
+ result()
190
+
191
+ #
192
+ # Template for the rendering stack node
193
+ #
194
+ # @private
195
+ #
196
+ __renderingStackElement: (parent=null) ->
197
+ metamorphBindings: []
198
+ locals: null
199
+ template: null
200
+ children: []
201
+ parent: parent
202
+
203
+ #
204
+ # Creates new rendering stack node using given pointer as the parent
205
+ #
206
+ # @private
207
+ #
208
+ __renderingStackChildFor: (parentPointer) ->
209
+ if !@__renderingStack
210
+ @__renderingStack = []
211
+
212
+ if !parentPointer
213
+ element = @__renderingStackElement()
214
+ @__renderingStack.push element
215
+ element
216
+ else
217
+ element = @__renderingStackElement parentPointer
218
+ parentPointer.children.push element
219
+ element
220
+
221
+ #
222
+ # Disables and unbinds all dynamic bindings for the whole rendering stack
223
+ #
224
+ # @private
225
+ #
226
+ __removeMetamorphs: (stackPointer=false) ->
227
+ remove = (stackPointer) =>
228
+ if stackPointer?.children
229
+ for child in stackPointer.children
145
230
  @__removeMetamorphs child
146
- stack.children = []
147
- morph.html result()
148
-
149
- # This is here to break stack tree and save from
150
- # repeating DOM modification
151
- timeout = null
152
- debouncedUpdate = ->
153
- clearTimeout timeout
154
- timeout = setTimeout update, 0
155
-
156
- for key, object of locals
157
- if locals.hasOwnProperty key
158
- if object?.bind? && object?.unbind?
159
- binding = [object, object.bind('changed', debouncedUpdate)]
160
- stack.metamorphBindings.push binding
161
-
162
- morph.__bindings = stack.metamorphBindings
163
-
164
- morph.outerHTML()
165
- else
166
- result()
167
-
168
- #
169
- # Template for the rendering stack node
170
- #
171
- __renderingStackElement: (parent=null) ->
172
- metamorphBindings: []
173
- locals: null
174
- template: null
175
- children: []
176
- parent: parent
177
-
178
- #
179
- # Creates new rendering stack node using given pointer as the parent
180
- #
181
- __renderingStackChildFor: (parentPointer) ->
182
- if !@__renderingStack
183
- @__renderingStack = []
184
-
185
- if !parentPointer
186
- element = @__renderingStackElement()
187
- @__renderingStack.push element
188
- element
189
- else
190
- element = @__renderingStackElement parentPointer
191
- parentPointer.children.push element
192
- element
193
-
194
- #
195
- # Disables and unbinds all dynamic bindings for the whole rendering stack
196
- #
197
- __removeMetamorphs: (stackPointer=false) ->
198
- remove = (stackPointer) =>
199
- if stackPointer?.children
200
- for child in stackPointer.children
201
- @__removeMetamorphs child
202
-
203
- if stackPointer?.metamorphBindings
204
- for [object, callback] in stackPointer.metamorphBindings
205
- object.unbind callback
206
- stackPointer.metamorphBindings = []
207
-
208
- unless stackPointer
209
- if @__renderingStack?
210
- remove stackPointer for stackPointer in @__renderingStack
211
-
212
- else
213
- remove stackPointer
231
+
232
+ if stackPointer?.metamorphBindings
233
+ for [object, callback] in stackPointer.metamorphBindings
234
+ object.unbind callback
235
+ stackPointer.metamorphBindings = []
236
+
237
+ unless stackPointer
238
+ if @__renderingStack?
239
+ remove stackPointer for stackPointer in @__renderingStack
240
+
241
+ else
242
+ remove stackPointer
214
243
 
215
244
  # AMD wrapper
216
245
  if define?.amd?