joosy 0.1.0.RC1

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