joosy 0.1.0.RC1

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 (98) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +14 -0
  3. data/Gemfile.lock +159 -0
  4. data/Guardfile +30 -0
  5. data/MIT-LICENSE +21 -0
  6. data/README.rdoc +3 -0
  7. data/Rakefile +14 -0
  8. data/app/assets/javascripts/joosy.js.coffee +7 -0
  9. data/app/assets/javascripts/joosy/core/application.js.coffee +28 -0
  10. data/app/assets/javascripts/joosy/core/form.js.coffee +87 -0
  11. data/app/assets/javascripts/joosy/core/helpers.js.coffee +6 -0
  12. data/app/assets/javascripts/joosy/core/joosy.js.coffee +65 -0
  13. data/app/assets/javascripts/joosy/core/layout.js.coffee +47 -0
  14. data/app/assets/javascripts/joosy/core/modules/container.js.coffee +59 -0
  15. data/app/assets/javascripts/joosy/core/modules/events.js.coffee +35 -0
  16. data/app/assets/javascripts/joosy/core/modules/filters.js.coffee +39 -0
  17. data/app/assets/javascripts/joosy/core/modules/log.js.coffee +15 -0
  18. data/app/assets/javascripts/joosy/core/modules/module.js.coffee +43 -0
  19. data/app/assets/javascripts/joosy/core/modules/renderer.js.coffee +116 -0
  20. data/app/assets/javascripts/joosy/core/modules/time_manager.js.coffee +25 -0
  21. data/app/assets/javascripts/joosy/core/modules/widgets_manager.js.coffee +58 -0
  22. data/app/assets/javascripts/joosy/core/page.js.coffee +156 -0
  23. data/app/assets/javascripts/joosy/core/preloader.js.coffee +5 -0
  24. data/app/assets/javascripts/joosy/core/resource/generic.js.coffee +61 -0
  25. data/app/assets/javascripts/joosy/core/resource/rest.js.coffee +76 -0
  26. data/app/assets/javascripts/joosy/core/resource/rest_collection.js.coffee +48 -0
  27. data/app/assets/javascripts/joosy/core/router.js.coffee +89 -0
  28. data/app/assets/javascripts/joosy/core/templaters/rails_jst.js.coffee +20 -0
  29. data/app/assets/javascripts/joosy/core/widget.js.coffee +41 -0
  30. data/app/assets/javascripts/joosy/preloaders/caching.js.coffee +94 -0
  31. data/app/assets/javascripts/joosy/preloaders/inline.js.coffee +55 -0
  32. data/app/helpers/joosy/sprockets_helper.rb +23 -0
  33. data/app/views/layouts/json_wrapper.json.erb +1 -0
  34. data/joosy.gemspec +25 -0
  35. data/lib/joosy.rb +9 -0
  36. data/lib/joosy/forms.rb +47 -0
  37. data/lib/joosy/rails/engine.rb +17 -0
  38. data/lib/joosy/rails/version.rb +5 -0
  39. data/lib/rails/generators/joosy/application_generator.rb +41 -0
  40. data/lib/rails/generators/joosy/joosy_base.rb +30 -0
  41. data/lib/rails/generators/joosy/layout_generator.rb +32 -0
  42. data/lib/rails/generators/joosy/page_generator.rb +44 -0
  43. data/lib/rails/generators/joosy/preloader_generator.rb +32 -0
  44. data/lib/rails/generators/joosy/resource_generator.rb +29 -0
  45. data/lib/rails/generators/joosy/templates/app.js.coffee +10 -0
  46. data/lib/rails/generators/joosy/templates/app/helpers/application.js.coffee +4 -0
  47. data/lib/rails/generators/joosy/templates/app/layouts/application.js.coffee +2 -0
  48. data/lib/rails/generators/joosy/templates/app/layouts/template.js.coffee +2 -0
  49. data/lib/rails/generators/joosy/templates/app/pages/application.js.coffee +1 -0
  50. data/lib/rails/generators/joosy/templates/app/pages/template.js.coffee +5 -0
  51. data/lib/rails/generators/joosy/templates/app/pages/welcome/index.js.coffee +22 -0
  52. data/lib/rails/generators/joosy/templates/app/resources/template.js.coffee +2 -0
  53. data/lib/rails/generators/joosy/templates/app/routes.js.coffee +8 -0
  54. data/lib/rails/generators/joosy/templates/app/templates/layouts/application.jst.hamlc +2 -0
  55. data/lib/rails/generators/joosy/templates/app/templates/pages/welcome/index.jst.hamlc +7 -0
  56. data/lib/rails/generators/joosy/templates/app/widgets/template.js.coffee +2 -0
  57. data/lib/rails/generators/joosy/templates/app_controller.rb +9 -0
  58. data/lib/rails/generators/joosy/templates/app_preloader.js.coffee.erb +13 -0
  59. data/lib/rails/generators/joosy/templates/preload.html.erb +26 -0
  60. data/lib/rails/generators/joosy/templates/preload.html.haml +19 -0
  61. data/lib/rails/generators/joosy/widget_generator.rb +32 -0
  62. data/spec/javascripts/helpers/spec_helper.js.coffee +44 -0
  63. data/spec/javascripts/joosy/core/application_spec.js.coffee +40 -0
  64. data/spec/javascripts/joosy/core/form_spec.js.coffee +141 -0
  65. data/spec/javascripts/joosy/core/joosy_spec.js.coffee +72 -0
  66. data/spec/javascripts/joosy/core/layout_spec.js.coffee +50 -0
  67. data/spec/javascripts/joosy/core/modules/container_spec.js.coffee +92 -0
  68. data/spec/javascripts/joosy/core/modules/events_spec.js.coffee +53 -0
  69. data/spec/javascripts/joosy/core/modules/filters_spec.js.coffee +71 -0
  70. data/spec/javascripts/joosy/core/modules/log_spec.js.coffee +15 -0
  71. data/spec/javascripts/joosy/core/modules/module_spec.js.coffee +47 -0
  72. data/spec/javascripts/joosy/core/modules/renderer_spec.js.coffee +149 -0
  73. data/spec/javascripts/joosy/core/modules/time_manager_spec.js.coffee +25 -0
  74. data/spec/javascripts/joosy/core/modules/widget_manager_spec.js.coffee +75 -0
  75. data/spec/javascripts/joosy/core/page_spec.js.coffee +175 -0
  76. data/spec/javascripts/joosy/core/resource/generic_spec.js.coffee +35 -0
  77. data/spec/javascripts/joosy/core/resource/rest_collection_spec.js.coffee +65 -0
  78. data/spec/javascripts/joosy/core/resource/rest_spec.js.coffee +108 -0
  79. data/spec/javascripts/joosy/core/router_spec.js.coffee +123 -0
  80. data/spec/javascripts/joosy/core/templaters/rails_jst_spec.js.coffee +25 -0
  81. data/spec/javascripts/joosy/core/widget_spec.js.coffee +51 -0
  82. data/spec/javascripts/joosy/preloaders/caching_spec.js.coffee +36 -0
  83. data/spec/javascripts/joosy/preloaders/inline_spec.js.coffee +16 -0
  84. data/spec/javascripts/support/assets/coolface.jpg +0 -0
  85. data/spec/javascripts/support/assets/okay.jpg +0 -0
  86. data/spec/javascripts/support/assets/test.js +1 -0
  87. data/spec/javascripts/support/jasmine.yml +74 -0
  88. data/spec/javascripts/support/jasmine_config.rb +23 -0
  89. data/spec/javascripts/support/jasmine_runner.rb +32 -0
  90. data/spec/javascripts/support/sinon-1.3.1.js +3469 -0
  91. data/spec/javascripts/support/sinon-ie-1.3.1.js +82 -0
  92. data/vendor/assets/javascripts/base64.js +135 -0
  93. data/vendor/assets/javascripts/inflection.js +656 -0
  94. data/vendor/assets/javascripts/jquery.form.js +980 -0
  95. data/vendor/assets/javascripts/jquery.hashchange.js +390 -0
  96. data/vendor/assets/javascripts/metamorph.js +409 -0
  97. data/vendor/assets/javascripts/sugar.js +6040 -0
  98. metadata +232 -0
@@ -0,0 +1,47 @@
1
+ #= require joosy/core/joosy
2
+ #= require joosy/core/modules/module
3
+ #= require joosy/core/modules/log
4
+ #= require joosy/core/modules/events
5
+ #= require joosy/core/modules/container
6
+ #= require joosy/core/modules/renderer
7
+ #= require joosy/core/modules/time_manager
8
+ #= require joosy/core/modules/widgets_manager
9
+ #= require joosy/core/modules/filters
10
+
11
+ class Joosy.Layout extends Joosy.Module
12
+ @include Joosy.Modules.Log
13
+ @include Joosy.Modules.Events
14
+ @include Joosy.Modules.Container
15
+ @include Joosy.Modules.Renderer
16
+ @include Joosy.Modules.TimeManager
17
+ @include Joosy.Modules.WidgetsManager
18
+ @include Joosy.Modules.Filters
19
+
20
+ @view 'layouts/default'
21
+
22
+ @beforePaint: (callback) -> @::__beforePaint = callback
23
+ @paint: (callback) -> @::__paint = callback
24
+ @erase: (callback) -> @::__erase = callback
25
+
26
+ navigate: (args...) ->
27
+ Joosy.Router.navigate(args...)
28
+
29
+ __renderSection: ->
30
+ 'layouts'
31
+
32
+ __load: (@container) ->
33
+ @refreshElements()
34
+ @__delegateEvents()
35
+ @__setupWidgets()
36
+ @__runAfterLoads()
37
+
38
+ __unload: ->
39
+ @clearTime()
40
+ @__unloadWidgets()
41
+ @__runAfterUnloads()
42
+
43
+ yield: ->
44
+ @uuid = Joosy.uuid()
45
+
46
+ content: ->
47
+ $("##{@uuid}")
@@ -0,0 +1,59 @@
1
+ Joosy.Modules.Container =
2
+ events: false
3
+ elements: false
4
+
5
+ eventSplitter: /^(\S+)\s*(.*)$/
6
+
7
+ $: (selector) -> $(selector, @container)
8
+
9
+ refreshElements: ->
10
+ @__collectElements().each (key, value) =>
11
+ @[key] = @$(value)
12
+
13
+ swapContainer: (container, data) ->
14
+ realContainer = container.clone().html(data)
15
+ container.replaceWith realContainer
16
+ realContainer
17
+
18
+ __collectElements: ->
19
+ elements = Object.extended(@elements || {})
20
+
21
+ klass = this
22
+ while klass = klass.constructor.__super__
23
+ elements.merge(klass.elements, false)
24
+
25
+ elements
26
+
27
+ __collectEvents: ->
28
+ events = Object.extended(@events || {})
29
+
30
+ klass = this
31
+ while klass = klass.constructor.__super__
32
+ events.merge(klass.events, false)
33
+
34
+ events
35
+
36
+ __extractSelector: (selector) ->
37
+ if r = selector.match(/\$([A-z]+)/)
38
+ selector = @__collectElements()[r[1]]
39
+
40
+ selector
41
+
42
+ __delegateEvents: ->
43
+ module = this
44
+ events = @__collectEvents()
45
+
46
+ events.each (key, method) =>
47
+ method = @[method] unless typeof(method) == 'function'
48
+ callback = (event) -> method.call(module, this, event)
49
+
50
+ match = key.match(@eventSplitter)
51
+ eventName = match[1]
52
+ selector = @__extractSelector(match[2])
53
+
54
+ if selector == ""
55
+ @container.bind(eventName, callback)
56
+ Joosy.Modules.Log.debugAs @, "#{eventName} binded on container"
57
+ else
58
+ @container.on(eventName, selector, callback)
59
+ Joosy.Modules.Log.debugAs @, "#{eventName} binded on #{selector}"
@@ -0,0 +1,35 @@
1
+ Joosy.Modules.Events =
2
+ wait: (events, callback) ->
3
+ events = events.split(/\s+/)
4
+
5
+ @__oneShotEvents ||= []
6
+ @__oneShotEvents.push [events, callback]
7
+
8
+ bind: (events, callback) ->
9
+ events = events.split(/\s+/)
10
+
11
+ @__boundEvents ||= []
12
+ @__boundEvents.push [events, callback]
13
+
14
+ unbind: (target) ->
15
+ for [events, callback], index in @__boundEvents
16
+ if callback == target
17
+ @__boundEvents.splice(index, 1)
18
+ return
19
+
20
+ trigger: (event) ->
21
+ Joosy.Modules.Log.debugAs @, "Event #{event} triggered"
22
+ if @__oneShotEvents
23
+ for [events, callback], index in @__oneShotEvents
24
+ position = events.indexOf(event)
25
+ events.splice(position, 1) if position >= 0
26
+
27
+ if events.length == 0
28
+ @__oneShotEvents.splice(index, 1)
29
+
30
+ callback()
31
+
32
+ if @__boundEvents
33
+ for [ events, callback ] in @__boundEvents
34
+ if events.has(event)
35
+ callback()
@@ -0,0 +1,39 @@
1
+ Joosy.Modules.Filters =
2
+ included: ->
3
+ @beforeLoad = (callback) ->
4
+ unless @::hasOwnProperty('__beforeLoads')
5
+ @::__beforeLoads = [].concat @.__super__.__beforeLoads || []
6
+ @::__beforeLoads.push callback
7
+
8
+ @afterLoad = (callback) ->
9
+ unless @::hasOwnProperty('__afterLoads')
10
+ @::__afterLoads = [].concat @.__super__.__afterLoads || []
11
+ @::__afterLoads.push callback
12
+
13
+ @afterUnload = (callback) ->
14
+ unless @::hasOwnProperty('__afterUnloads')
15
+ @::__afterUnloads = [].concat @.__super__.__afterUnloads || []
16
+ @::__afterUnloads.push callback
17
+
18
+ __runBeforeLoads: (opts...) ->
19
+ return true unless @__beforeLoads?.length > 0
20
+
21
+ flag = true
22
+
23
+ for filter in @__beforeLoads
24
+ filter = @[filter] unless typeof(filter) is 'function'
25
+ flag = flag && filter.apply(@, opts)
26
+
27
+ return flag
28
+
29
+ __runAfterLoads: (opts...) ->
30
+ if @__afterLoads?
31
+ for filter in @__afterLoads
32
+ filter = @[filter] unless typeof(filter) is 'function'
33
+ filter.apply(@, opts)
34
+
35
+ __runAfterUnloads: (opts...) ->
36
+ if @__afterUnloads?
37
+ for filter in @__afterUnloads
38
+ filter = @[filter] unless typeof(filter) is 'function'
39
+ filter.apply(@, opts)
@@ -0,0 +1,15 @@
1
+ Joosy.Modules.Log =
2
+ log: (args...) ->
3
+ return if typeof console is 'undefined'
4
+
5
+ if console.log.apply?
6
+ args.unshift "Joosy>"
7
+ console.log(args...)
8
+ else
9
+ console.log(args.first())
10
+
11
+ debug: (args...) ->
12
+ @log(args...) if Joosy.debug
13
+
14
+ debugAs: (context, string, args...) ->
15
+ @debug "#{Joosy.Module.__className__(context) || 'unknown context'}> #{string}", args...
@@ -0,0 +1,43 @@
1
+ moduleKeywords = ['included', 'extended']
2
+
3
+ class Joosy.Module
4
+ @__namespace__: []
5
+
6
+ @__className__ = (klass) ->
7
+ klass = klass.constructor unless Object.isFunction(klass)
8
+
9
+ if klass.name?
10
+ klass.name
11
+ else
12
+ klass.toString().replace(/^function ([a-zA-Z]+)\([\s\S]+/, '$1')
13
+
14
+ @hasAncestor = (what, klass) ->
15
+ return false unless what?
16
+
17
+ [ what, klass ] = [ what.prototype, klass.prototype ]
18
+
19
+ while what
20
+ return true if what == klass
21
+ what = what.constructor?.__super__
22
+
23
+ false
24
+
25
+ @include: (obj) ->
26
+ throw new Error 'include(obj) requires obj' unless obj
27
+
28
+ Object.extended(obj).each (key, value) =>
29
+ if key not in moduleKeywords
30
+ this::[key] = value
31
+
32
+ obj.included?.apply(this)
33
+ this
34
+
35
+ @extend: (obj) ->
36
+ throw new Error 'extend(obj) requires obj' unless obj
37
+
38
+ Object.extended(obj).each (key, value) =>
39
+ if key not in moduleKeywords
40
+ this[key] = value
41
+
42
+ obj.extended?.apply(this)
43
+ this
@@ -0,0 +1,116 @@
1
+ #= require_tree ../templaters
2
+ #= require metamorph
3
+
4
+ Joosy.Modules.Renderer =
5
+
6
+ __renderer: ->
7
+ throw new Error "#{@constructor.name} does not have an attached template"
8
+
9
+ __helpers: null
10
+
11
+ included: ->
12
+ @view = (template) ->
13
+ if Object.isFunction(template)
14
+ @::__renderer = template
15
+ else
16
+ @::__renderer = (locals={}) ->
17
+ @render(template, locals)
18
+
19
+ @helpers = (helpers...) ->
20
+ @::__helpers ||= []
21
+ helpers.map (helper) =>
22
+ module = Joosy.Helpers[helper]
23
+ unless module
24
+ throw new Error "Cannot find helper module #{helper}"
25
+
26
+ @::__helpers.push module
27
+
28
+ @::__helpers = @::__helpers.unique()
29
+
30
+ __instantiateHelpers: ->
31
+ unless @__helpersInstance
32
+ @__helpersInstance = Object.extended Joosy.Helpers.Application
33
+
34
+ @__helpersInstance.render = =>
35
+ @render(arguments...)
36
+
37
+ @__helpersInstance.widget = (element, widget) =>
38
+ @widgets ||= {}
39
+
40
+ uuid = Joosy.uuid()
41
+ element = document.createElement(element)
42
+ temp = document.createElement("div")
43
+
44
+ element.id = uuid
45
+ @widgets['#'+uuid] = widget
46
+
47
+ temp.appendChild(element)
48
+ temp.innerHTML
49
+
50
+ if @__helpers
51
+ for helper in @__helpers
52
+ @__helpersInstance.merge helper
53
+
54
+ @__helpersInstance
55
+
56
+ # If we do not have __proto__ available...
57
+ __proxifyHelpers: (locals) ->
58
+ if locals.hasOwnProperty '__proto__'
59
+ locals.__proto__ = @__instantiateHelpers()
60
+
61
+ locals
62
+ else
63
+ unless @__helpersProxyInstance
64
+ @__helpersProxyInstance = (locals) ->
65
+ Object.merge(this, locals)
66
+
67
+ @__helpersProxyInstance.prototype = @__instantiateHelpers()
68
+
69
+ new @__helpersProxyInstance(locals)
70
+
71
+ render: (template, locals={}) ->
72
+ isResource = Joosy.Module.hasAncestor(locals.constructor, Joosy.Resource.Generic)
73
+
74
+ if Object.isString template
75
+ if @__renderSection?
76
+ template = Joosy.Application.templater.resolveTemplate @__renderSection(), template, this
77
+
78
+ template = Joosy.Application.templater.buildView template
79
+ else if !Object.isFunction(template)
80
+ throw new Error "#{Joosy.Module.__className__ @}> template (maybe @view) does not look like a string or lambda"
81
+
82
+ if !Object.isObject(locals) && !isResource
83
+ throw new Error "#{Joosy.Module.__className__ @}> locals (maybe @data?) can only be dumb hash or Resource"
84
+
85
+ # Small code dup due to the fact we sometimes
86
+ # actually CLONE object when proxying helpers
87
+ if !isResource
88
+ locals = @__proxifyHelpers(locals)
89
+ morph = Metamorph template(locals)
90
+ update = => morph.html template(locals)
91
+ else
92
+ locals.e = @__proxifyHelpers(locals.e)
93
+ morph = Metamorph template(locals.e)
94
+ update = => morph.html template(locals.e)
95
+
96
+ # This is here to break stack tree and save from
97
+ # repeating DOM handling
98
+ update = update.debounce(0)
99
+
100
+ @__metamorphs ||= []
101
+
102
+ if isResource
103
+ locals.bind 'changed', update
104
+ else
105
+ for key, object of locals
106
+ if locals.hasOwnProperty key
107
+ if object?.bind? && object?.unbind?
108
+ object.bind 'changed', update
109
+ @__metamorphs.push [object, update]
110
+
111
+ morph.outerHTML()
112
+
113
+ __removeMetamorphs: ->
114
+ if @__metamorphs
115
+ for [object, callback] in @__metamorphs
116
+ object.unbind callback
@@ -0,0 +1,25 @@
1
+ Joosy.Modules.TimeManager =
2
+ setTimeout: (timeout, action) ->
3
+ @__timeouts ||= []
4
+
5
+ timer = window.setTimeout (=> action()), timeout
6
+ @__timeouts.push timer
7
+
8
+ timer
9
+
10
+ setInterval: (delay, action) ->
11
+ @__intervals ||= []
12
+
13
+ timer = window.setInterval (=> action()), delay
14
+ @__intervals.push timer
15
+
16
+ timer
17
+
18
+ clearTime: ->
19
+ if @__intervals
20
+ for entry in @__intervals
21
+ window.clearInterval entry
22
+
23
+ if @__timeouts
24
+ for entry in @__timeouts
25
+ window.clearTimeout entry
@@ -0,0 +1,58 @@
1
+ Joosy.Modules.WidgetsManager =
2
+ registerWidget: (container, widget) ->
3
+ if Joosy.Module.hasAncestor(widget, Joosy.Widget)
4
+ widget = new widget()
5
+
6
+ @__activeWidgets ||= []
7
+ @__activeWidgets.push widget.__load(this, $(container))
8
+
9
+ widget
10
+
11
+ unregisterWidget: (widget) ->
12
+ widget.__unload()
13
+
14
+ @__activeWidgets.splice(@__activeWidgets.indexOf(widget), 1)
15
+
16
+ __collectWidgets: ->
17
+ widgets = Object.extended(@widgets || {})
18
+
19
+ klass = this
20
+ while klass = klass.constructor.__super__
21
+ widgets.merge(klass.widgets, false)
22
+
23
+ widgets
24
+
25
+ __setupWidgets: ->
26
+ widgets = @__collectWidgets()
27
+ registered = Object.extended()
28
+
29
+ widgets.each (selector, widget) =>
30
+ if selector == '$container'
31
+ activeSelector = @container
32
+ else
33
+ if r = selector.match(/\$([A-z_]+)/)
34
+ selector = @elements[r[1]]
35
+
36
+ activeSelector = $(selector, @container)
37
+
38
+ registered[selector] = Object.extended()
39
+
40
+ activeSelector.each (index, elem) =>
41
+ if Joosy.Module.hasAncestor(widget, Joosy.Widget)
42
+ instance = new widget
43
+ else
44
+ instance = widget.call this, index
45
+
46
+ registered[selector][Joosy.Module.__className__ instance] ||= 0
47
+ registered[selector][Joosy.Module.__className__ instance] += 1
48
+
49
+ @registerWidget($(elem), instance)
50
+
51
+ registered.each (selector, value) =>
52
+ value.each (widget, count) =>
53
+ Joosy.Modules.Log.debugAs @, "Widget #{widget} registered at '#{selector}'. Elements: #{count}"
54
+
55
+ __unloadWidgets: ->
56
+ if @__activeWidgets
57
+ for widget in @__activeWidgets
58
+ widget.__unload()