From @catmando on May 14, 2015 22:18
This is WIP, just to see what you think of the changes.
For now its using my own copy of opal that better source mapping than the current version 0.7...
Summary of changes for discussion:
Any class with a render method can be used in the dsl by using the class name.
class MyComponent
...
end
class ParentComponent
def render
div do
MyComponent param1: 'foo' # works same as presents MyComponent ...
end
end
end
Component classes can also be scoped normally, so in the above example MyComponent
could be a child, sibling, etc of ParentComponent
. So if ParentComponents
full name was Components::Admin::ParentComponent
, then MyComponent
could be a child of Components, Components::Admin, or Components::Admin::ParentComponent
.
In the case where MyComponent
was somewhere else in the module hierarchy the name can be qualified as needed. i.e. Shared::MyComponent
params can be specified as one a liner (similar to state, and rails attributes etc.)
required_param :foo
optional_param :bar, default: "xyz"
params get their own read accessors just like state, so you can say
params with type Proc
, can be called inside the component without using call.
required_param :inform_of_update, type: Proc
...
inform_of_update "new text string"
state can now be updated by using the ! methods.
some_state! 12 # state now updated to 12, returns the previous value
some_state! # without a param returns an object that will observe any changes to some_state
some_state! << 12 << 13 # assuming some_state is an array for example
the object returned by some_state! will also respond to :call, this allows
state to be easily passed as a Proc to a child param. For example
my_component inform_of_update: my_state!
# compare to the equivalent
present MyComponent, inform_of_update: -> (x) { self.my_state = x }
params can be of type React::Observable. Params of this type act like a Proc with an initial value. They can be read in the usual way, and can be update by calling the ! version. So they work like react linkages. State variables with the ! on the end are actually Observables, so you can now simply write:
class MyInput
required_param :value, type: React::Observable
def render
input(value: value).on(:change) { |new_value| value! new_value }
end
end
class Parent
define_state(:my_state) { "initial value"}
def render
MyInput value! # value will be updated by MyInput
end
end
If needed you can create an observable on the fly by calling watch(initial_value) { | updated_value | ... }
A copy of the current state is kept, so it can be read during events.
ReactJS does not guarantee the value of state being stable until after
the event completes.
So now its safe to write
my_state! get_new_value
my_state.each do { ... } #etc
# instead of
temp = get_new_value
temp.each do { ... } #etc
The React.render method can be passed a Opal Element directly (no need to do a get(0).
React.render(element, Element['#todoapp'])
# works the same as
React.render(element, Element['#todoapp'].get(0)) # this still works
There is a React::TopLevelComponent class. This provides several features:
- ability to create multiple react components mounted in various places on a single page and communicating. Very handy for evolving from other frameworks, as it allows subsections of the page to be redone as components.
- automatically mounts the components on DOM ready
- provides a safe mechanism to communicate from outside the React structure
class App < React::TopLevelComponent
define_state :text do "I have never been clicked" end
mount_component FriendsContainer, 'div#placeholder'
mount_component JustSomeText, 'div#placeholder2' do {text: text} end
external_update :update_text do |new_text| text! new_text end
end
elsewhere in old js code you can say
$(document).ready(function() {
var n = 0;
$('#jquery-button').click(function() {
Opal.App.$update_text("I have been clicked "+n+"times")
}}
Any calls to external updates will be queued until the top level component is mounted
The TopLevelComponent class can be defined across multiple files. This means it can be used to control the app layout across multiple pages, with each page adding its own "top level" behavior inside the page.
Well I think that's it... interested in what you think... I am using all these changes for my main company app, and should be deploying soon, so you can see it live in action.
I am happy to write the docs, update the samples, and of course write tests for all these.
Copied from original issue: zetachang/react.rb/pull/32