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

Opal 0.8 - WIP noise reducing syntax changes #32

@sollycatprint

Description

@sollycatprint

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

puts foo
puts bar

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:

  1. 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.
  2. automatically mounts the components on DOM ready
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions