redson 0.0.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.
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: []