reactive-ruby 0.7.28 → 0.7.29

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +6 -0
  3. data/Gemfile.lock +4 -1
  4. data/README.md +132 -68
  5. data/Rakefile +5 -2
  6. data/example/examples/Gemfile +0 -2
  7. data/example/rails-tutorial/Gemfile +3 -2
  8. data/lib/generators/reactive_ruby/test_app/templates/application.rb +11 -0
  9. data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/application.rb +2 -0
  10. data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +3 -0
  11. data/lib/generators/reactive_ruby/test_app/templates/boot.rb +6 -0
  12. data/lib/generators/reactive_ruby/test_app/templates/script/rails +5 -0
  13. data/lib/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb +11 -0
  14. data/lib/generators/reactive_ruby/test_app/templates/views/components/todo.rb +14 -0
  15. data/lib/generators/reactive_ruby/test_app/test_app_generator.rb +105 -0
  16. data/lib/rails-helpers/top_level_rails_component.rb +9 -16
  17. data/lib/{reactive-ruby → react}/api.rb +8 -65
  18. data/lib/{reactive-ruby → react}/callbacks.rb +0 -0
  19. data/lib/react/component.rb +266 -0
  20. data/lib/react/component/api.rb +48 -0
  21. data/lib/react/component/class_methods.rb +183 -0
  22. data/lib/{reactive-ruby → react}/element.rb +10 -11
  23. data/lib/{reactive-ruby → react}/event.rb +0 -0
  24. data/lib/{reactive-ruby → react}/ext/hash.rb +0 -0
  25. data/lib/{reactive-ruby → react}/ext/string.rb +0 -0
  26. data/lib/react/native_library.rb +57 -0
  27. data/lib/{reactive-ruby → react}/observable.rb +0 -4
  28. data/lib/{reactive-ruby → react}/rendering_context.rb +0 -6
  29. data/lib/{reactive-ruby → react}/state.rb +3 -6
  30. data/lib/{reactive-ruby → react}/top_level.rb +2 -3
  31. data/lib/react/validator.rb +127 -0
  32. data/lib/reactive-ruby.rb +20 -20
  33. data/lib/reactive-ruby/version.rb +1 -1
  34. data/{opal-spec/reactjs → spec}/index.html.erb +1 -1
  35. data/{opal-spec → spec/react}/callbacks_spec.rb +8 -9
  36. data/{opal-spec → spec/react}/component_spec.rb +170 -120
  37. data/spec/react/dsl_spec.rb +16 -0
  38. data/{opal-spec → spec/react}/element_spec.rb +7 -20
  39. data/{opal-spec → spec/react}/event_spec.rb +3 -1
  40. data/spec/react/native_library_spec.rb +10 -0
  41. data/spec/react/param_declaration_spec.rb +18 -0
  42. data/{opal-spec → spec/react}/react_spec.rb +3 -2
  43. data/spec/react/react_state_spec.rb +22 -0
  44. data/spec/react/top_level_component_spec.rb +68 -0
  45. data/{opal-spec → spec/react}/tutorial/tutorial_spec.rb +11 -13
  46. data/{opal-spec → spec/react}/validator_spec.rb +50 -4
  47. data/spec/reactive-ruby/isomorphic_helpers_spec.rb +22 -4
  48. data/spec/spec_helper.rb +51 -0
  49. data/spec/support/react/spec_helpers.rb +57 -0
  50. data/spec/vendor/es5-shim.min.js +6 -0
  51. metadata +56 -24
  52. data/lib/reactive-ruby/component.rb +0 -502
  53. data/lib/reactive-ruby/validator.rb +0 -99
  54. data/old-readme +0 -220
  55. data/opal-spec/spec_helper.rb +0 -29
@@ -0,0 +1,183 @@
1
+ module React
2
+ module Component
3
+ module ClassMethods
4
+ def backtrace(*args)
5
+ @backtrace_off = (args[0] == :off)
6
+ end
7
+
8
+ def process_exception(e, component, reraise = nil)
9
+ message = ["Exception raised while rendering #{component}"]
10
+ if !@backtrace_off
11
+ message << " #{e.backtrace[0]}"
12
+ message += e.backtrace[1..-1].collect { |line| line }
13
+ else
14
+ message[0] += ": #{e.message}"
15
+ end
16
+ message = message.join("\n")
17
+ `console.error(message)`
18
+ raise e if reraise
19
+ end
20
+
21
+ def validator
22
+ @validator ||= React::Validator.new
23
+ end
24
+
25
+ def prop_types
26
+ if self.validator
27
+ {
28
+ _componentValidator: %x{
29
+ function(props, propName, componentName) {
30
+ var errors = #{validator.validate(Hash.new(`props`))};
31
+ var error = new Error(#{"In component `" + self.name + "`\n" + `errors`.join("\n")});
32
+ return #{`errors`.count > 0 ? `error` : `undefined`};
33
+ }
34
+ }
35
+ }
36
+ else
37
+ {}
38
+ end
39
+ end
40
+
41
+ def default_props
42
+ validator.default_props
43
+ end
44
+
45
+ def params(&block)
46
+ validator.build(&block)
47
+ end
48
+
49
+ def define_param_method(name, param_type)
50
+ if param_type == React::Observable
51
+ (@two_way_params ||= []) << name
52
+ define_method("#{name}") do
53
+ params[name].instance_variable_get("@value") if params[name]
54
+ end
55
+ define_method("#{name}!") do |*args|
56
+ return unless params[name]
57
+ if args.count > 0
58
+ current_value = params[name].instance_variable_get("@value")
59
+ params[name].call args[0]
60
+ current_value
61
+ else
62
+ current_value = params[name].instance_variable_get("@value")
63
+ params[name].call current_value unless @dont_update_state rescue nil # rescue in case we in middle of render
64
+ params[name]
65
+ end
66
+ end
67
+ elsif param_type == Proc
68
+ define_method("#{name}") do |*args, &block|
69
+ params[name].call(*args, &block) if params[name]
70
+ end
71
+ else
72
+ define_method("#{name}") do
73
+ @processed_params[name] ||= if param_type.respond_to? :_react_param_conversion
74
+ param_type._react_param_conversion params[name]
75
+ elsif param_type.is_a? Array and param_type[0].respond_to? :_react_param_conversion
76
+ params[name].collect { |param| param_type[0]._react_param_conversion param }
77
+ else
78
+ params[name]
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ def required_param(name, options = {})
85
+ validator.requires(name, options)
86
+ define_param_method(name, options[:type])
87
+ end
88
+
89
+ alias_method :require_param, :required_param
90
+
91
+ def optional_param(name, options = {})
92
+ validator.optional(name, options)
93
+ define_param_method(name, options[:type]) unless name == :params
94
+ end
95
+
96
+ def collect_other_params_as(name)
97
+ validator.allow_undefined_props = true
98
+ define_method(name) do
99
+ @_all_others ||= self.class.validator.undefined_props(props)
100
+ end
101
+ end
102
+
103
+ def define_state(*states, &block)
104
+ default_initial_value = (block and block.arity == 0) ? yield : nil
105
+ states_hash = (states.last.is_a? Hash) ? states.pop : {}
106
+ states.each { |name| states_hash[name] = default_initial_value }
107
+ (self.initial_state ||= {}).merge! states_hash
108
+ states_hash.each do |name, initial_value|
109
+ define_state_methods(self, name, &block)
110
+ end
111
+ end
112
+
113
+ def export_state(*states, &block)
114
+ default_initial_value = (block and block.arity == 0) ? yield : nil
115
+ states_hash = (states.last.is_a? Hash) ? states.pop : {}
116
+ states.each { |name| states_hash[name] = default_initial_value }
117
+ React::State.initialize_states(self, states_hash)
118
+ states_hash.each do |name, initial_value|
119
+ define_state_methods(self, name, self, &block)
120
+ define_state_methods(singleton_class, name, self, &block)
121
+ end
122
+ end
123
+
124
+ def define_state_methods(this, name, from = nil, &block)
125
+ this.define_method("#{name}") do
126
+ React::State.get_state(from || self, name)
127
+ end
128
+ this.define_method("#{name}=") do |new_state|
129
+ yield name, React::State.get_state(from || self, name), new_state if block and block.arity > 0
130
+ React::State.set_state(from || self, name, new_state)
131
+ end
132
+ this.define_method("#{name}!") do |*args|
133
+ #return unless @native
134
+ if args.count > 0
135
+ yield name, React::State.get_state(from || self, name), args[0] if block and block.arity > 0
136
+ current_value = React::State.get_state(from || self, name)
137
+ React::State.set_state(from || self, name, args[0])
138
+ current_value
139
+ else
140
+ current_state = React::State.get_state(from || self, name)
141
+ yield name, React::State.get_state(from || self, name), current_state if block and block.arity > 0
142
+ React::State.set_state(from || self, name, current_state)
143
+ React::Observable.new(current_state) do |update|
144
+ yield name, React::State.get_state(from || self, name), update if block and block.arity > 0
145
+ React::State.set_state(from || self, name, update)
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ def native_mixin(item)
152
+ native_mixins << item
153
+ end
154
+
155
+ def native_mixins
156
+ @native_mixins ||= []
157
+ end
158
+
159
+ def static_call_back(name, &block)
160
+ static_call_backs[name] = block
161
+ end
162
+
163
+ def static_call_backs
164
+ @static_call_backs ||= {}
165
+ end
166
+
167
+ def export_component(opts = {})
168
+ export_name = (opts[:as] || name).split("::")
169
+ first_name = export_name.first
170
+ Native(`window`)[first_name] = add_item_to_tree(Native(`window`)[first_name], [React::API.create_native_react_class(self)] + export_name[1..-1].reverse).to_n
171
+ end
172
+
173
+ def add_item_to_tree(current_tree, new_item)
174
+ if Native(current_tree).class != Native::Object or new_item.length == 1
175
+ new_item.inject do |memo, sub_name| {sub_name => memo} end
176
+ else
177
+ Native(current_tree)[new_item.last] = add_item_to_tree(Native(current_tree)[new_item.last], new_item[0..-2])
178
+ current_tree
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
@@ -1,4 +1,4 @@
1
- require "reactive-ruby/ext/string"
1
+ require 'react/ext/string'
2
2
 
3
3
  module React
4
4
  class Element
@@ -6,11 +6,11 @@ module React
6
6
 
7
7
  alias_native :element_type, :type
8
8
  alias_native :props, :props
9
-
9
+
10
10
  attr_reader :type
11
11
  attr_reader :properties
12
12
  attr_reader :block
13
-
13
+
14
14
  attr_accessor :waiting_on_resources
15
15
 
16
16
  def initialize(native_element, type, properties, block)
@@ -39,22 +39,22 @@ module React
39
39
  @properties.merge! props
40
40
  self
41
41
  end
42
-
42
+
43
43
  def render(props = {}) # for rendering children
44
44
  if props.empty?
45
45
  React::RenderingContext.render(self)
46
46
  else
47
47
  React::RenderingContext.render(
48
48
  Element.new(
49
- `React.cloneElement(#{self.to_n}, #{API.convert_props(props)})`,
50
- type,
51
- properties.merge(props),
49
+ `React.cloneElement(#{self.to_n}, #{API.convert_props(props)})`,
50
+ type,
51
+ properties.merge(props),
52
52
  block
53
53
  )
54
54
  )
55
55
  end
56
56
  end
57
-
57
+
58
58
  def method_missing(class_name, args = {}, &new_block)
59
59
  class_name = class_name.split("__").collect { |s| s.gsub("_", "-") }.join("_")
60
60
  new_props = properties.dup
@@ -65,14 +65,13 @@ module React
65
65
  React::RenderingContext.build { React::RenderingContext.render(type, new_props, &new_block) }
66
66
  )
67
67
  end
68
-
68
+
69
69
  def as_node
70
70
  RenderingContext.as_node(self)
71
71
  end
72
-
72
+
73
73
  def delete
74
74
  RenderingContext.delete(self)
75
75
  end
76
-
77
76
  end
78
77
  end
File without changes
File without changes
File without changes
@@ -0,0 +1,57 @@
1
+ module React
2
+ class NativeLibrary
3
+ def self.renames_and_exclusions
4
+ @renames_and_exclusions ||= {}
5
+ end
6
+
7
+ def self.libraries
8
+ @libraries ||= []
9
+ end
10
+
11
+ def self.const_missing(name)
12
+ if renames_and_exclusions.has_key? name
13
+ if native_name = renames_and_exclusions[name]
14
+ native_name
15
+ else
16
+ super
17
+ end
18
+ else
19
+ libraries.each do |library|
20
+ native_name = "#{library}.#{name}"
21
+ native_component = `eval(#{native_name})` rescue nil
22
+ React::API.import_native_component(name, native_component) and return name if native_component and `native_component != undefined`
23
+ end
24
+ name
25
+ end
26
+ end
27
+
28
+ def self.method_missing(n, *args, &block)
29
+ name = n
30
+ if name =~ /_as_node$/
31
+ node_only = true
32
+ name = name.gsub(/_as_node$/, "")
33
+ end
34
+ unless name = const_get(name)
35
+ return super
36
+ end
37
+ if node_only
38
+ React::RenderingContext.build { React::RenderingContext.render(name, *args, &block) }.to_n
39
+ else
40
+ React::RenderingContext.render(name, *args, &block)
41
+ end
42
+ rescue
43
+ end
44
+
45
+ def self.imports(library)
46
+ libraries << library
47
+ end
48
+
49
+ def self.rename(rename_list={})
50
+ renames_and_exclusions.merge!(rename_list.invert)
51
+ end
52
+
53
+ def self.exclude(*exclude_list)
54
+ renames_and_exclusions.merge(Hash[exclude_list.map {|k| [k, nil]}])
55
+ end
56
+ end
57
+ end
@@ -1,7 +1,5 @@
1
1
  module React
2
-
3
2
  class Observable
4
-
5
3
  def initialize(value, on_change = nil, &block)
6
4
  @value = value
7
5
  @on_change = on_change || block
@@ -27,7 +25,5 @@ module React
27
25
  def to_proc
28
26
  lambda { |arg = @value| @on_change.call arg }
29
27
  end
30
-
31
28
  end
32
-
33
29
  end
@@ -1,7 +1,5 @@
1
1
  module React
2
-
3
2
  class RenderingContext
4
-
5
3
  class << self
6
4
  attr_accessor :waiting_on_resources
7
5
  end
@@ -76,11 +74,9 @@ module React
76
74
  value.as_node if value.is_a? Element rescue nil
77
75
  end if args[0] and args[0].is_a? Hash
78
76
  end
79
-
80
77
  end
81
78
 
82
79
  class ::Object
83
-
84
80
  alias_method :old_method_missing, :method_missing
85
81
 
86
82
  ["span", "para", "td", "th", "while_loading"].each do |tag|
@@ -101,7 +97,5 @@ module React
101
97
  return self.method_missing(*["br"]) if self.is_a? React::Component
102
98
  React::RenderingContext.render("span") { React::RenderingContext.render(self.to_s); React::RenderingContext.render("br") }
103
99
  end
104
-
105
100
  end
106
-
107
101
  end
@@ -1,9 +1,6 @@
1
1
  module React
2
-
3
2
  class State
4
-
5
3
  class << self
6
-
7
4
  attr_reader :current_observer
8
5
 
9
6
  def initialize_states(object, initial_values) # initialize objects' name/value pairs
@@ -17,9 +14,12 @@ module React
17
14
  end
18
15
 
19
16
  def set_state2(object, name, value) # set object's name state to value, tell all observers it has changed. Observers must implement update_react_js_state
17
+ object_needs_notification = object.respond_to? :update_react_js_state
20
18
  observers_by_name[object][name].dup.each do |observer|
21
19
  observer.update_react_js_state(object, name, value)
20
+ object_needs_notification = false if object == observer
22
21
  end
22
+ object.update_react_js_state(nil, name, value) if object_needs_notification
23
23
  end
24
24
 
25
25
  def set_state(object, name, value, delay=nil)
@@ -110,9 +110,6 @@ module React
110
110
  def observers_by_name
111
111
  @observers_by_name ||= Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = [] } }
112
112
  end
113
-
114
113
  end
115
-
116
114
  end
117
-
118
115
  end
@@ -1,6 +1,6 @@
1
1
  require "native"
2
2
  require 'active_support'
3
- require 'reactive-ruby/component'
3
+ require 'react/component'
4
4
 
5
5
  module React
6
6
  HTML_TAGS = %w(a abbr address area article aside audio b base bdi bdo big blockquote body br
@@ -22,7 +22,7 @@ module React
22
22
  readOnly rel required role rows rowSpan sandbox scope scrolling seamless
23
23
  selected shape size sizes span spellCheck src srcDoc srcSet start step style
24
24
  tabIndex target title type useMap value width wmode dangerouslySetInnerHTML)
25
-
25
+
26
26
  def self.create_element(type, properties = {}, &block)
27
27
  React::API.create_element(type, properties, &block)
28
28
  end
@@ -49,5 +49,4 @@ module React
49
49
  def self.unmount_component_at_node(node)
50
50
  `React.unmountComponentAtNode(node.$$class ? node[0] : node)`
51
51
  end
52
-
53
52
  end
@@ -0,0 +1,127 @@
1
+ module React
2
+ class Validator
3
+ attr_accessor :errors
4
+ private :errors
5
+
6
+ def self.build(&block)
7
+ self.new.build(&block)
8
+ end
9
+
10
+ def build(&block)
11
+ instance_eval(&block)
12
+ self
13
+ end
14
+
15
+ def requires(name, options = {})
16
+ options[:required] = true
17
+ define_rule(name, options)
18
+ end
19
+
20
+ def optional(name, options = {})
21
+ options[:required] = false
22
+ define_rule(name, options)
23
+ end
24
+
25
+ def allow_undefined_props=(allow)
26
+ @allow_undefined_props = allow
27
+ end
28
+
29
+ def undefined_props(props)
30
+ self.allow_undefined_props = true
31
+ props.reject { |name, value| rules[name] }
32
+ end
33
+
34
+ def validate(props)
35
+ self.errors = []
36
+ validate_undefined(props) unless allow_undefined_props?
37
+ props = coerce_native_hash_values(defined_props(props))
38
+ validate_required(props)
39
+ props.each do |name, value|
40
+ validate_types(name, value)
41
+ validate_allowed(name, value)
42
+ end
43
+ errors
44
+ end
45
+
46
+ def default_props
47
+ rules
48
+ .select {|key, value| value.keys.include?("default") }
49
+ .inject({}) {|memo, (k,v)| memo[k] = v[:default]; memo}
50
+ end
51
+
52
+ private
53
+
54
+ def defined_props(props)
55
+ props.select { |name| rules.keys.include?(name) }
56
+ end
57
+
58
+ def allow_undefined_props?
59
+ !!@allow_undefined_props
60
+ end
61
+
62
+ def rules
63
+ @rules ||= { children: { required: false } }
64
+ end
65
+
66
+ def define_rule(name, options = {})
67
+ rules[name] = coerce_native_hash_values(options)
68
+ end
69
+
70
+ def errors
71
+ @errors ||= []
72
+ end
73
+
74
+ def validate_types(prop_name, value)
75
+ return unless klass = rules[prop_name][:type]
76
+ if klass.is_a?(Array) && klass.length > 0
77
+ validate_value_array(prop_name, value)
78
+ else
79
+ allow_nil = !!rules[prop_name][:allow_nil]
80
+ type_check("`#{prop_name}`", value, klass, allow_nil)
81
+ end
82
+ end
83
+
84
+ def type_check(prop_name, value, klass, allow_nil)
85
+ return if allow_nil && value.nil?
86
+ return if value.is_a?(klass)
87
+ return if klass.respond_to?(:_react_param_conversion) &&
88
+ klass._react_param_conversion(value, :validate_only)
89
+ errors << "Provided prop #{prop_name} could not be converted to #{klass}"
90
+ end
91
+
92
+ def validate_allowed(prop_name, value)
93
+ return unless values = rules[prop_name][:values]
94
+ return if values.include?(value)
95
+ errors << "Value `#{value}` for prop `#{prop_name}` is not an allowed value"
96
+ end
97
+
98
+ def validate_required(props)
99
+ (rules.keys - props.keys).each do |name|
100
+ next unless rules[name][:required]
101
+ errors << "Required prop `#{name}` was not specified"
102
+ end
103
+ end
104
+
105
+ def validate_undefined(props)
106
+ (props.keys - rules.keys).each do |prop_name|
107
+ errors << "Provided prop `#{prop_name}` not specified in spec"
108
+ end
109
+ end
110
+
111
+ def validate_value_array(name, value)
112
+ klass = rules[name][:type]
113
+ allow_nil = !!rules[name][:allow_nil]
114
+ value.each_with_index do |item, index|
115
+ type_check("`#{name}`[#{index}]", Native(item), klass[0], allow_nil)
116
+ end
117
+ rescue NoMethodError
118
+ errors << "Provided prop `#{name}` was not an Array"
119
+ end
120
+
121
+ def coerce_native_hash_values(hash)
122
+ hash.each do |key, value|
123
+ hash[key] = Native(value)
124
+ end
125
+ end
126
+ end
127
+ end