Skip to content
This repository has been archived by the owner on Oct 19, 2018. It is now read-only.

Commit

Permalink
closes #271 #269 #268 #267
Browse files Browse the repository at this point in the history
  • Loading branch information
catmando committed Jul 23, 2018
1 parent b9653fa commit 64b457c
Show file tree
Hide file tree
Showing 14 changed files with 228 additions and 140 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
source 'https://rubygems.org'
gem "opal-jquery", git: "https://github.com/opal/opal-jquery.git", branch: "master"
gem 'hyperloop-config', git: 'https://github.com/ruby-hyperloop/hyperloop-config.git', branch: 'edge'
gemspec
9 changes: 5 additions & 4 deletions lib/hyper-react.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,25 @@ module Hyperloop
class Component
end
end
require 'react/top_level'
require 'react/top_level_render'
require 'native'
require 'react/observable'
require 'react/validator'
require 'react/element'
require 'react/api'
require 'react/component'
require 'react/component/dsl_instance_methods'
require 'react/component/should_component_update'
require 'react/component/tags'
require 'react/component/base'
require 'react/element'
require 'react/event'
require 'react/api'
require 'react/rendering_context'
require 'react/state'
require 'react/object'
require 'react/to_key'
require 'react/ext/opal-jquery/element'
require 'reactive-ruby/isomorphic_helpers'
require 'react/top_level'
require 'react/top_level_render'
require 'rails-helpers/top_level_rails_component'
require 'reactive-ruby/version'
module Hyperloop
Expand Down
162 changes: 87 additions & 75 deletions lib/react/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,94 +44,106 @@ def self.create_native_react_class(type)
raise "Provided class should define `render` method" if !(type.method_defined? :render)
render_fn = (type.method_defined? :_render_wrapper) ? :_render_wrapper : :render
# this was hashing type.to_s, not sure why but .to_s does not work as it Foo::Bar::View.to_s just returns "View"
@@component_classes[type] ||= %x{
class extends React.Component {
constructor(props) {
super(props);
this.mixins = #{type.respond_to?(:native_mixins) ? type.native_mixins : `[]`};
this.statics = #{type.respond_to?(:static_call_backs) ? type.static_call_backs.to_n : `{}`};
this.state = {};
this.__opalInstanceInitializedState = false;
this.__opalInstanceSyncSetState = true;
this.__opalInstance = #{type.new(`this`)};
this.__opalInstanceInitializedState = true;
this.__opalInstanceSyncSetState = false;
this.__name = #{type.name};
}
static get displayName() {
if (typeof this.__name != "undefined") {
return this.__name;
} else {
return #{type.name};
}
}
static set displayName(name) {
this.__name = name;
}
static get defaultProps() {
return #{type.respond_to?(:default_props) ? type.default_props.to_n : `{}`};
}
static get propTypes() {
return #{type.respond_to?(:prop_types) ? type.prop_types.to_n : `{}`};
}
componentWillMount() {
if (#{type.method_defined? :component_will_mount}) {

@@component_classes[type] ||= begin
comp = %x{
class extends React.Component {
constructor(props) {
super(props);
this.mixins = #{type.respond_to?(:native_mixins) ? type.native_mixins : `[]`};
this.statics = #{type.respond_to?(:static_call_backs) ? type.static_call_backs.to_n : `{}`};
this.state = {};
this.__opalInstanceInitializedState = false;
this.__opalInstanceSyncSetState = true;
this.__opalInstance.$component_will_mount();
this.__opalInstance = #{type.new(`this`)};
this.__opalInstanceInitializedState = true;
this.__opalInstanceSyncSetState = false;
this.__name = #{type.name};
}
}
componentDidMount() {
this.__opalInstance.is_mounted = true
if (#{type.method_defined? :component_did_mount}) {
this.__opalInstanceSyncSetState = false;
this.__opalInstance.$component_did_mount();
static get displayName() {
if (typeof this.__name != "undefined") {
return this.__name;
} else {
return #{type.name};
}
}
}
componentWillReceiveProps(next_props) {
if (#{type.method_defined? :component_will_receive_props}) {
this.__opalInstanceSyncSetState = true;
this.__opalInstance.$component_will_receive_props(Opal.Hash.$new(next_props));
this.__opalInstanceSyncSetState = false;
static set displayName(name) {
this.__name = name;
}
}
shouldComponentUpdate(next_props, next_state) {
if (#{type.method_defined? :should_component_update?}) {
this.__opalInstanceSyncSetState = false;
return this.__opalInstance["$should_component_update?"](Opal.Hash.$new(next_props), Opal.Hash.$new(next_state));
} else { return true; }
}
componentWillUpdate(next_props, next_state) {
if (#{type.method_defined? :component_will_update}) {
this.__opalInstanceSyncSetState = false;
this.__opalInstance.$component_will_update(Opal.Hash.$new(next_props), Opal.Hash.$new(next_state));
static get defaultProps() {
return #{type.respond_to?(:default_props) ? type.default_props.to_n : `{}`};
}
}
componentDidUpdate(prev_props, prev_state) {
if (#{type.method_defined? :component_did_update}) {
this.__opalInstanceSyncSetState = false;
this.__opalInstance.$component_did_update(Opal.Hash.$new(prev_props), Opal.Hash.$new(prev_state));
static get propTypes() {
return #{type.respond_to?(:prop_types) ? type.prop_types.to_n : `{}`};
}
}
componentWillUnmount() {
if (#{type.method_defined? :component_will_unmount}) {
componentWillMount() {
if (#{type.method_defined? :component_will_mount}) {
this.__opalInstanceSyncSetState = true;
this.__opalInstance.$component_will_mount();
this.__opalInstanceSyncSetState = false;
}
}
componentDidMount() {
this.__opalInstance.is_mounted = true
if (#{type.method_defined? :component_did_mount}) {
this.__opalInstanceSyncSetState = false;
this.__opalInstance.$component_did_mount();
}
}
componentWillReceiveProps(next_props) {
if (#{type.method_defined? :component_will_receive_props}) {
this.__opalInstanceSyncSetState = true;
this.__opalInstance.$component_will_receive_props(Opal.Hash.$new(next_props));
this.__opalInstanceSyncSetState = false;
}
}
shouldComponentUpdate(next_props, next_state) {
if (#{type.method_defined? :should_component_update?}) {
this.__opalInstanceSyncSetState = false;
return this.__opalInstance["$should_component_update?"](Opal.Hash.$new(next_props), Opal.Hash.$new(next_state));
} else { return true; }
}
componentWillUpdate(next_props, next_state) {
if (#{type.method_defined? :component_will_update}) {
this.__opalInstanceSyncSetState = false;
this.__opalInstance.$component_will_update(Opal.Hash.$new(next_props), Opal.Hash.$new(next_state));
}
}
componentDidUpdate(prev_props, prev_state) {
if (#{type.method_defined? :component_did_update}) {
this.__opalInstanceSyncSetState = false;
this.__opalInstance.$component_did_update(Opal.Hash.$new(prev_props), Opal.Hash.$new(prev_state));
}
}
componentWillUnmount() {
if (#{type.method_defined? :component_will_unmount}) {
this.__opalInstanceSyncSetState = false;
this.__opalInstance.$component_will_unmount();
}
this.__opalInstance.is_mounted = false;
}
render() {
this.__opalInstanceSyncSetState = false;
this.__opalInstance.$component_will_unmount();
return this.__opalInstance.$send(render_fn).$to_n();
}
this.__opalInstance.is_mounted = false;
}
componentDidCatch(error, info) {
if (#{type.method_defined? :component_did_catch}) {
}
# check to see if there is an after_error callback. If there is add a
# componentDidCatch handler. Because legacy behavior is to allow any object
# that responds to render to act as a component we have to make sure that
# we have a callbacks_for method. This all becomes much easier once issue
# #270 is resolved.
if type.respond_to?(:callbacks_for) && type.callbacks_for(:after_error) != []
%x{
comp.prototype.componentDidCatch = function(error, info) {
this.__opalInstanceSyncSetState = false;
this.__opalInstance.$component_did_catch(error, Opal.Hash.$new(info));
}
}
render() {
this.__opalInstanceSyncSetState = false;
return this.__opalInstance.$send(render_fn).$to_n();
}
}
}
end
comp
end
end

def self.create_element(type, properties = {}, &block)
Expand Down Expand Up @@ -182,7 +194,7 @@ def self.convert_props(properties)

elsif key == "key"
props["key"] = value.to_key

elsif key == 'ref' && value.is_a?(Proc)
props[key] = %x{
function(dom_node){
Expand Down
8 changes: 8 additions & 0 deletions lib/react/children.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ def initialize(children)
@children = children
end

def render
each(&:render)
end

def to_proc
-> () { render }
end

def each(&block)
return to_enum(__callee__) { length } unless block_given?
return [] unless length > 0
Expand Down
18 changes: 6 additions & 12 deletions lib/react/component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
require 'react/component/api'
require 'react/component/class_methods'
require 'react/component/props_wrapper'
require 'native'

module Hyperloop
class Component
Expand Down Expand Up @@ -70,10 +69,13 @@ def component_will_receive_props(next_props)
# need to rethink how this works in opal-react, or if its actually that useful within the react.rb environment
# for now we are just using it to clear processed_params
React::State.set_state_context_to(self) { self.run_callback(:before_receive_props, next_props) }
@_receiving_props = true
end

def component_will_update(next_props, next_state)
React::State.set_state_context_to(self) { self.run_callback(:before_update, next_props, next_state) }
params._reset_all_others_cache if @_receiving_props
@_receiving_props = false
end

def component_did_update(prev_props, prev_state)
Expand All @@ -92,15 +94,7 @@ def component_will_unmount

def component_did_catch(error, info)
React::State.set_state_context_to(self) do
if self.class.callbacks_for(:after_error) == []
if `typeof error.$backtrace === "function"`
`console.error(error.$backtrace().$join("\n"))`
else
`console.error(error, info)`
end
else
self.run_callback(:after_error, error, info)
end
self.run_callback(:after_error, error, info)
end
end

Expand All @@ -109,7 +103,7 @@ def component_did_catch(error, info)
def update_react_js_state(object, name, value)
if object
name = "#{object.class}.#{name}" unless object == self
# Date.now() has only millisecond precision, if several notifications of
# Date.now() has only millisecond precision, if several notifications of
# observer happen within a millisecond, updates may get lost.
# to mitigate this the Math.random() appends some random number
# this way notifactions will happen as expected by the rest of hyperloop
Expand All @@ -125,7 +119,7 @@ def update_react_js_state(object, name, value)
def set_state_synchronously?
@native.JS[:__opalInstanceSyncSetState]
end

def render
raise 'no render defined'
end unless method_defined?(:render)
Expand Down
13 changes: 2 additions & 11 deletions lib/react/component/class_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,16 +92,7 @@ def param(*args)
end

def collect_other_params_as(name)
validator.allow_undefined_props = true
validator_in_lexical_scope = validator
props_wrapper.define_method(name) do
@_all_others ||= validator_in_lexical_scope.undefined_props(props)
end

validator_in_lexial_scope = validator
props_wrapper.define_method(name) do
@_all_others ||= validator_in_lexial_scope.undefined_props(props)
end
validator.all_other_params(name) { props }
end

def define_state(*states, &block)
Expand Down Expand Up @@ -173,7 +164,7 @@ def add_item_to_tree(current_tree, new_item)
end
end

def to_n
def to_n
React::API.class_eval('@@component_classes')[self]
end
end
Expand Down
12 changes: 12 additions & 0 deletions lib/react/component/props_wrapper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ def self.define_param(name, param_type)
end
end

def self.define_all_others(name)
define_method("#{name}") do
@_all_others_cache ||= yield(props)
end
end


def initialize(component)
@component = component
end
Expand All @@ -52,6 +59,11 @@ def [](prop)
props[prop]
end


def _reset_all_others_cache
@_all_others_cache = nil
end

private

def fetch_from_cache(name)
Expand Down
23 changes: 20 additions & 3 deletions lib/react/component/tags.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ def present(component, *params, &children)
React::RenderingContext.render(component, *params, &children)
end

# define each predefined tag as an instance method
# define each predefined tag (downcase) as an instance method (deprecated) and as a component (upcase)
HTML_TAGS.each do |tag|

# deprecated - remove
if tag == 'p'
define_method(tag) do |*params, &children|
if children || params.count == 0 || (params.count == 1 && params.first.is_a?(Hash))
Expand All @@ -35,8 +37,23 @@ def present(component, *params, &children)
React::RenderingContext.render(tag, *params, &children)
end
end
alias_method tag.upcase, tag
const_set tag.upcase, tag

# new style: allows custom hooks to be added and/or the render method to
# be modified. i.e. see how hyper-mesh deals with defaultValues in input tags

klass = Class.new(Hyperloop::Component) do
# its complicated but the automatic inclusion of the Mixin is setup after all
# the files are loaded, so at this point we have to manually load it.
include Hyperloop::Component::Mixin
collect_other_params_as :opts
# we simply pass along all the params and children with the tag string name
render { React::RenderingContext.render(tag, params.opts, &children) }

# after_error do |error, info|
# raise error
# end
end
const_set(tag.upcase, klass)
end

def self.html_tag_class_for(tag)
Expand Down
Loading

0 comments on commit 64b457c

Please sign in to comment.