redson 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7bd4e9b64e97c83a614eeeecdd816bd8c66b4ad0
4
+ data.tar.gz: 31c813cb8cebf3d164c6f16ccabb2a25aa02fb21
5
+ SHA512:
6
+ metadata.gz: 6ac622ead6741509e73b4215c4dac2792181d590d8441a01f873a7f4ddf4bba450c30d3a07a3716c69cdcd5b848c9bdfbfc39f3ac3d0b8c4470bd4fa853d0ac7
7
+ data.tar.gz: c506406de60f5252992d38d38887a29c4f22f6ae91bc7da2ecd0aa50ab673ed1c57a0848b6083bdb60197d8265083667fdc7b035322ddd09628633cf68901911
data/lib/redson.rb ADDED
@@ -0,0 +1,6 @@
1
+ require "opal"
2
+ require "opal-jquery"
3
+
4
+ # Register our library code path with opal build tools
5
+ # This allows opal-rails to pick us up
6
+ Opal.append_path File.join(File.dirname(File.expand_path(__FILE__)), '..', 'opal')
@@ -0,0 +1,3 @@
1
+ module Redson
2
+ VERSION = "0.0.1"
3
+ end
data/opal/redson.rb ADDED
@@ -0,0 +1,9 @@
1
+ require "redson/ext/element"
2
+ require "redson/version"
3
+ require "redson/error"
4
+ require "redson/binding"
5
+ require "redson/observable"
6
+ require "redson/model"
7
+ require "redson/view"
8
+ require "redson/widget"
9
+ require "redson/form"
@@ -0,0 +1,34 @@
1
+ module Redson
2
+ class Binding
3
+ def initialize(element_matcher, options)
4
+ @element_matcher = element_matcher
5
+ @options = options
6
+ @options[:notify_on] ||= 'keyup'
7
+ validate!
8
+ end
9
+
10
+ def element_matcher
11
+ @element_matcher
12
+ end
13
+
14
+ def notify_on
15
+ @options[:notify_on]
16
+ end
17
+
18
+ def to
19
+ @options[:to]
20
+ end
21
+
22
+ def notification_handler
23
+ @options[:notification_handler]
24
+ end
25
+
26
+ def validate!
27
+ raise ":to missing when setting up binding for #{element_matcher}" unless to
28
+ end
29
+
30
+ def create_for(widget)
31
+ widget.bind(element_matcher, to, notify_on, notification_handler)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ require "redson/error/no_such_event_error"
2
+ require "redson/error/model_api_path_not_set_error"
3
+ require "redson/error/not_a_dom_element_error"
@@ -0,0 +1,9 @@
1
+ module Redson
2
+ module Error
3
+ class ModelApiPathNotSetError < StandardError
4
+ def initialize(model)
5
+ super("api_path is not set for #{model.class.name}")
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Redson
2
+ module Error
3
+ class NoSuchEventError < StandardError
4
+ def initialize(event_name)
5
+ super("No such event is emitted: #{event_name}")
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Redson
2
+ module Error
3
+ class NotADomElementError < StandardError
4
+ def initialize(thing)
5
+ super("Expected a jQuery DOM element, got #{thing} instead")
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ # reopening opal-jquery object
2
+ class Element
3
+ def self.find!(selector)
4
+ elements = self.find(selector)
5
+ raise "No elements matching #{selector} found" if elements.empty?
6
+ elements
7
+ end
8
+
9
+ def find!(selector)
10
+ elements = self.find(selector)
11
+ raise "No elements matching #{selector} found inside #{self.inspect}" if elements.empty?
12
+ elements
13
+ end
14
+
15
+ def input?
16
+ attr('data-rs-type') == 'input'
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ require "redson/form/model"
2
+ require "redson/form/view"
3
+ require "redson/form/widget"
@@ -0,0 +1,6 @@
1
+ module Redson
2
+ module Form
3
+ class Model < Redson::Model
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,47 @@
1
+ module Redson
2
+ module Form
3
+ class View < Redson::View
4
+ def initialize_view_elements
5
+ raise "#{this_element.inspect} is not a form element" unless this_element.is('form')
6
+ disable_submit_event_propagation!
7
+ write_current_view_state_to_model
8
+ trigger_save_on_submit
9
+ end
10
+
11
+ def trigger_save_on_submit
12
+ %x{
13
+ self.$this_element().on('submit', function(event) {
14
+ self.$model().$save();
15
+ });
16
+ }
17
+ end
18
+
19
+ def write_current_view_state_to_model
20
+ input_elements.each do |element|
21
+ model[element['name']] = element.value
22
+ end
23
+ model.api_path = self.action_attribute
24
+ end
25
+
26
+ def disable_submit_event_propagation!
27
+ %x{
28
+ self.$this_element().on('submit', function(event) {
29
+ event.preventDefault();
30
+ });
31
+ }
32
+ end
33
+
34
+ def action_attribute
35
+ this_element['action']
36
+ end
37
+
38
+ def submit_element
39
+ this_element.find('input[type=submit]')
40
+ end
41
+
42
+ def input_elements
43
+ this_element.find('input[type!=submit]')
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,6 @@
1
+ module Redson
2
+ module Form
3
+ class Widget < Redson::Widget
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,95 @@
1
+ class Redson::Model
2
+ include Redson::Observable
3
+
4
+ def self.validates_presence_of(key)
5
+ end
6
+
7
+ def initialize
8
+ @_redson_api_path = nil
9
+ @_redson_state = {}
10
+ @_redson_nested_keys = {}
11
+ @_redson_observers = `jQuery({})`
12
+ @_redson_attributes_namespace = nil
13
+ reset_errors
14
+ initialize_observable
15
+ end
16
+
17
+ def [](key)
18
+ @_redson_state[key]
19
+ end
20
+
21
+ def []=(key, value)
22
+ if @_redson_nested_keys.include?(key)
23
+ keys = @_redson_nested_keys[key]
24
+ @_redson_state[keys[0]] ||= {}
25
+ @_redson_state[keys[0]][keys[1]] = value
26
+ elsif `/\[/.test(#{key})`
27
+ keys = parse_keys(key)
28
+ @_redson_attributes_namespace = keys[0]
29
+ @_redson_nested_keys[key] = keys
30
+ @_redson_state[keys[0]] ||= {}
31
+ @_redson_state[keys[0]][keys[1]] = value
32
+ else
33
+ @_redson_state[key] = value
34
+ end
35
+ end
36
+
37
+ def attributes_namespace
38
+ @_redson_attributes_namespace
39
+ end
40
+
41
+ def reset_errors
42
+ @_redson_server_validation_errors = {}
43
+ end
44
+
45
+ def errors
46
+ @_redson_server_validation_errors
47
+ end
48
+
49
+ def valid?
50
+ errors.empty?
51
+ end
52
+
53
+ def api_path
54
+ @_redson_api_path
55
+ end
56
+
57
+ def api_path=(path)
58
+ @_redson_api_path = path.end_with?(".json") ? path : "#{path}.json"
59
+ end
60
+
61
+ def save
62
+ raise Redson::Error::ModelApiPathNotSetError.new(self) unless api_path
63
+ if self['_method'] == 'patch'
64
+ HTTP.put(api_path, :payload => @_redson_state) { |response| process_response(response) }
65
+ else
66
+ HTTP.post(api_path, :payload => @_redson_state) { |response| process_response(response) }
67
+ end
68
+ end
69
+
70
+ def process_response(response)
71
+ if response.status_code == 201
72
+ reset_errors
73
+ puts :created
74
+ notify_observers(:created)
75
+ elsif response.status_code == 200
76
+ reset_errors
77
+ puts :updated
78
+ notify_observers(:updated)
79
+ elsif response.status_code == 422
80
+ @_redson_server_validation_errors = response.json
81
+ puts :unprocessable_entity
82
+ notify_observers(:unprocessable_entity)
83
+ else
84
+ puts "Unhandled Response Status #{response.status_code}, expected 200, 201 or 422"
85
+ end
86
+ end
87
+
88
+ def parse_keys(nested_key)
89
+ nested_key.split(/\]\[|\[|\]/)
90
+ end
91
+
92
+ def inspect
93
+ "#{super}\napi_path: #{api_path}\nattributes:\n#{@_redson_state.inspect}\nerrors:\n#{errors.inspect}"
94
+ end
95
+ end
@@ -0,0 +1,38 @@
1
+ module Redson
2
+ module Observable
3
+ def self.scoped_event_name(event_name)
4
+ "redson.#{event_name}"
5
+ end
6
+
7
+ def initialize_observable
8
+ `this._redson_observers = jQuery({})`
9
+ @_redson_cached_observer_names = {}
10
+ end
11
+
12
+ def register_observer(observer, options = {})
13
+ event_name = options[:on]
14
+ handler_method_name = options[:notify]
15
+ handler_method_name = handler_method_name ? "#{handler_method_name}" : generate_default_handler_name_for(event_name)
16
+ handler_method_name = "$#{handler_method_name}"
17
+ scoped_event_name = Observable.scoped_event_name(event_name)
18
+ `this._redson_observers.on(scoped_event_name, jQuery.proxy(observer[handler_method_name], observer))`
19
+ self
20
+ end
21
+
22
+ def notify_observers(event_name)
23
+ scoped_event_name = Observable.scoped_event_name(event_name)
24
+ `this._redson_observers.trigger(scoped_event_name)`
25
+ self
26
+ end
27
+
28
+ def generate_default_handler_name_for(event_name)
29
+ if @_redson_cached_observer_names[event_name]
30
+ @_redson_cached_observer_names[event_name]
31
+ else
32
+ underscored_class_name = self.class.name.split("::").last.scan(/[A-Z][a-z]*/).join("_").downcase
33
+ default_handler_name = "#{underscored_class_name}_#{event_name}_handler"
34
+ @_redson_cached_observer_names[event_name] = default_handler_name
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,3 @@
1
+ module Redson
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,56 @@
1
+ class Redson::View
2
+ attr_reader :target_element, :this_element,
3
+ :template_element, :model, :form
4
+
5
+
6
+ def initialize(model, target_element, template_element = nil)
7
+ @target_element = target_element
8
+ @template_element = template_element
9
+ @this_element = template_element ? template_element.clone : target_element
10
+ @this_element = `jQuery(self.this_element)`
11
+ @model = model
12
+ initialize_view_elements
13
+ @model.register_observer(
14
+ self, :on => :created
15
+ ).register_observer(
16
+ self, :on => :updated
17
+ ).register_observer(
18
+ self, :on => :unprocessable_entity
19
+ )
20
+ end
21
+
22
+ def model_created_handler(event)
23
+ Kernel.p :model_created_handler
24
+ end
25
+
26
+ def model_updated_handler(event)
27
+ Kernel.p :model_updated_handler
28
+ end
29
+
30
+ def model_unprocessable_entity_handler(event)
31
+ Kernel.p :model_unprocessable_entity_handler
32
+ model.errors.each do |key, values|
33
+ target_element.find("input[name=#{model.attributes_namespace}\\[#{key}\\]]").add_class("error")
34
+ end
35
+ end
36
+
37
+ def initialize_view_elements
38
+ raise "initialize_view_elements must be overriden in #{self.class}"
39
+ end
40
+
41
+ def find_element!(matcher)
42
+ this_element.find!(matcher)
43
+ end
44
+
45
+ def render
46
+ target_element.append(this_element) unless target_element == this_element
47
+ end
48
+
49
+ def bind(element_matcher, to, event_name_to_update_on, notification_handler)
50
+ element = this_element.find!(element_matcher)
51
+ element.on(event_name_to_update_on) do |event|
52
+ model[to] = element.value
53
+ self.method(notification_handler).call(event)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,154 @@
1
+ class Redson::Widget
2
+ TEMPLATES_ELEMENT_MATCHER = "#r-templates"
3
+ TEMPLATE_ELEMENT_MATCHER = ".r-template"
4
+ ELEMENT_MATCHER = ".r-element"
5
+ INPUT_TYPE_ATTRIBUTE_MATCHER = "[data-rs-type='input']"
6
+
7
+ KEY_DEFAULTS = 'defaults'
8
+ KEY_BINDINGS = 'bindings'
9
+ KEY_WIDGET_NAME = 'widget_name'
10
+ KEY_TEMPLATE_ELEMENT_MATCHER = 'template_element_matcher'
11
+ KEY_TARGET_ELEMENT_MATCHER = 'target_element_matcher'
12
+ KEY_VIEW_KLASS_NAME = 'view_klass_name'
13
+ KEY_MODEL_KLASS_NAME = 'model_klass_name'
14
+ KEY_USE_TEMPLATE = 'use_template'
15
+
16
+ attr_reader :view
17
+
18
+ def self.inherited(base_klass)
19
+ base_klass.initialize_widget_klass
20
+ end
21
+
22
+ def initialize(target_element = nil)
23
+ raise Redson::Error::NotADomElementError.new(target_element) unless target_element.class == Element
24
+ @model = model_klass.new
25
+ @view = view_klass.new(
26
+ @model,
27
+ target_element || Element.find!(target_element_matcher),
28
+ using_template? ? Element.find!(template_element_matcher) : nil
29
+ )
30
+ setup_bindings
31
+ end
32
+
33
+ def setup_bindings
34
+ self.class.bindings.each do |binding|
35
+ binding.create_for(self)
36
+ end
37
+ end
38
+
39
+ def bind(element_matcher, to, event_name_to_update_on, notification_handler)
40
+ view.bind(element_matcher, to, event_name_to_update_on, notification_handler)
41
+ end
42
+
43
+ def render
44
+ view.render
45
+ end
46
+
47
+ def widget_name
48
+ self.class.widget_name
49
+ end
50
+
51
+ def target_element_matcher
52
+ self.class.target_element_matcher
53
+ end
54
+
55
+ def template_element_matcher
56
+ self.class.template_element_matcher
57
+ end
58
+
59
+ def using_template?
60
+ self.class.using_template?
61
+ end
62
+
63
+ def view_klass
64
+ self.class.view_klass
65
+ end
66
+
67
+ def model_klass
68
+ self.class.model_klass
69
+ end
70
+
71
+ def self.instantiate_all_in_document
72
+ widgets = []
73
+ Element.find(target_element_matcher).each do |target|
74
+ widgets << self.new(target)
75
+ end
76
+ widgets
77
+ end
78
+
79
+ def self.render_all_in_document
80
+ instantiate_all_in_document.each do |widget|
81
+ widget.render
82
+ end
83
+ end
84
+
85
+ def self.initialize_widget_klass
86
+ @_redson_widget_config = {}
87
+ @_redson_widget_config[KEY_DEFAULTS] = {}
88
+ @_redson_widget_config[KEY_BINDINGS] = []
89
+ @_redson_widget_config[KEY_WIDGET_NAME] = self.name.split(/::/)[-2].downcase
90
+ @_redson_widget_config[KEY_DEFAULTS][KEY_USE_TEMPLATE] = true
91
+ @_redson_widget_config[KEY_DEFAULTS][KEY_TEMPLATE_ELEMENT_MATCHER] = "#{TEMPLATE_ELEMENT_MATCHER}.#{widget_name}"
92
+ @_redson_widget_config[KEY_DEFAULTS][KEY_VIEW_KLASS_NAME] = "#{self.name}".sub("::Widget", "::View")
93
+ @_redson_widget_config[KEY_DEFAULTS][KEY_MODEL_KLASS_NAME] = "#{self.name}".sub("::Widget", "::Model")
94
+ end
95
+
96
+ def self.bind(element_matcher, options)
97
+ @_redson_widget_config[KEY_BINDINGS] << Binding.new(element_matcher, options)
98
+ end
99
+
100
+ def self.widget_name
101
+ @_redson_widget_config[KEY_WIDGET_NAME]
102
+ end
103
+
104
+ def self.bindings
105
+ @_redson_widget_config[KEY_BINDINGS]
106
+ end
107
+
108
+ def self.target_element_matcher
109
+ @_redson_widget_config[KEY_DEFAULTS][KEY_TARGET_ELEMENT_MATCHER]
110
+ end
111
+
112
+ # TODO
113
+ # We're using the 'set_' prefix to distinguish getters and setters
114
+ # Using the exact same name but different arity seems to confuse
115
+ # the Opal compiler, as does differentiating using '='
116
+ def self.set_target_element_matcher(matcher)
117
+ @_redson_widget_config[KEY_DEFAULTS][KEY_TARGET_ELEMENT_MATCHER] = matcher
118
+ end
119
+
120
+ def self.template_element_matcher
121
+ @_redson_widget_config[KEY_DEFAULTS][KEY_TEMPLATE_ELEMENT_MATCHER]
122
+ end
123
+
124
+ def self.set_template_element_matcher(matcher)
125
+ @_redson_widget_config[KEY_DEFAULTS][KEY_TEMPLATE_ELEMENT_MATCHER] = matcher
126
+ end
127
+
128
+ def self.disable_template!
129
+ @_redson_widget_config[KEY_DEFAULTS][KEY_USE_TEMPLATE] = false
130
+ end
131
+
132
+ def self.using_template?
133
+ @_redson_widget_config[KEY_DEFAULTS][KEY_USE_TEMPLATE]
134
+ end
135
+
136
+ def self.view_klass
137
+ # TODO
138
+ # Because Kernel.const_get("Foo::Bar::View") doesn't work on Opal
139
+ @view_klass ||= load_fully_qualified_constant(@_redson_widget_config[KEY_DEFAULTS][KEY_VIEW_KLASS_NAME])
140
+ end
141
+
142
+ def self.model_klass
143
+ # TODO
144
+ # Because Kernel.const_get("Foo::Bar::View") doesn't work on Opal
145
+ @model_klass ||= load_fully_qualified_constant(@_redson_widget_config[KEY_DEFAULTS][KEY_MODEL_KLASS_NAME])
146
+ end
147
+
148
+ private
149
+ def self.load_fully_qualified_constant(constant_string)
150
+ constant_string.split("::").inject(Kernel) do |const, name|
151
+ const.const_get(name)
152
+ end
153
+ end
154
+ end
metadata ADDED
@@ -0,0 +1,148 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: redson
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Sidu Ponnappa
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: opal-jquery
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: opal-rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '4.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '4.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard-livereload
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: guard-rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Redson is a browser based MVC component framework written in Ruby using
98
+ the Opal ruby-to-js compiler.
99
+ email:
100
+ - ckponnappa@gmail.com
101
+ executables: []
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - lib/redson.rb
106
+ - lib/redson/version.rb
107
+ - opal/redson.rb
108
+ - opal/redson/binding.rb
109
+ - opal/redson/error.rb
110
+ - opal/redson/error/model_api_path_not_set_error.rb
111
+ - opal/redson/error/no_such_event_error.rb
112
+ - opal/redson/error/not_a_dom_element_error.rb
113
+ - opal/redson/ext/element.rb
114
+ - opal/redson/form.rb
115
+ - opal/redson/form/model.rb
116
+ - opal/redson/form/view.rb
117
+ - opal/redson/form/widget.rb
118
+ - opal/redson/model.rb
119
+ - opal/redson/observable.rb
120
+ - opal/redson/version.rb
121
+ - opal/redson/view.rb
122
+ - opal/redson/widget.rb
123
+ homepage: http://github.com/kaiwren/redson
124
+ licenses:
125
+ - MIT
126
+ metadata: {}
127
+ post_install_message:
128
+ rdoc_options: []
129
+ require_paths:
130
+ - lib
131
+ required_ruby_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ required_rubygems_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ requirements: []
142
+ rubyforge_project:
143
+ rubygems_version: 2.2.2
144
+ signing_key:
145
+ specification_version: 4
146
+ summary: Redson is a browser based MVC component framework written in Ruby using the
147
+ Opal ruby-to-js compiler.
148
+ test_files: []