Skip to content

Commit 5e7b24b

Browse files
committed
more doc updates
1 parent 959c224 commit 5e7b24b

17 files changed

+884
-241
lines changed

docs/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
_book/
2+
node_modules/

docs/SUMMARY.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
* [Using the Hyperstack Installer](rails-installation/using-the-installer.md)
77
* [Using the Generators](rails-installation/generators.md)
88
* [File Structure](rails-installation/file-structure.md)
9+
* [Routing and Mounting Components](rails-installation/routing-and-mounting-components.md)
910
* [Other Rails Configuration Details](rails-installation/other-details.md)
10-
* [Why Rails and Other Frameworks](rails-installation/why-rails.md)
11+
* [Why Rails? Other Frameworks?](rails-installation/why-rails.md)
1112
* [Client DSL](client-dsl/README.md)
1213
* [HTML & CSS DSL](client-dsl/html-css.md)
13-
* [Component DSL](client-dsl/components.md)
14+
* [Component DSL](client-dsl/component-basics.md)
15+
* [Component Children, Keys and Fragments](client-dsl/component-details.md)
1416
* [Lifecycle Methods](client-dsl/lifecycle-methods.md)
1517
* [State](client-dsl/state.md)
1618
* [Event Handlers](client-dsl/event-handlers.md)
@@ -19,6 +21,8 @@
1921
* [Stores](client-dsl/hyper-store.md)
2022
* [Elements and Rendering](client-dsl/elements-and-rendering.md)
2123
* [Further Reading](client-dsl/further-reading.md)
24+
* [List of Predefined Tags and Components](client-dsl/predefined-tags.md)
25+
* [Notes](client-dsl/notes.md)
2226
* [Isomorphic DSL](isomorphic-dsl/README.md)
2327
* [Isomorphic Models](isomorphic-dsl/hyper-model.md)
2428
* [Isomorphic Operations](isomorphic-dsl/hyper-operation.md)
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# HyperComponent DSL - Basics
2+
3+
The Hyperstack Component DSL is a set of class and instance methods that are used to describe React components and render the user-interface.
4+
5+
The DSL has the following major areas:
6+
7+
* The `HyperComponent` class
8+
* HTML DSL elements
9+
* Component Lifecycle Methods \(`before_mount`, `after_mount`, `after_update`\)
10+
* The `param` and `render` methods
11+
* Event handlers
12+
* Miscellaneous methods
13+
14+
## Defining a Component
15+
16+
Hyperstack Components are Ruby classes that inherit from the `HyperComponent` base class:
17+
18+
```ruby
19+
class MyComponent < HyperComponent
20+
...
21+
end
22+
```
23+
> **[More on the HyperComponent base class](notes.html#the-hypercomponent-base-class)**
24+
25+
## The `render` Callback
26+
27+
At a minimum every *concrete* component class must define a `render` block which generates one or more child elements. Those children may in turn have an arbitrarily deep structure. **[More on concrete and abstract components](notes.html#abstract-and-concrete-components)**
28+
29+
```ruby
30+
class Component < HyperComponent
31+
render do
32+
DIV { } # render an empty div
33+
end
34+
end
35+
```
36+
> The code between the `do` and `end` is called a block. **[More here...](notes.html#blocks-in-ruby)**
37+
38+
To save a little typing you can also specify the top level element to be rendered:
39+
40+
```ruby
41+
class Component < HyperComponent
42+
render(DIV, class: 'my-special-class') do
43+
# everything will be rendered in a div
44+
end
45+
end
46+
```
47+
48+
To render a component, you reference its class name as a method call from another component. This creates a new instance, passes any parameters and proceeds with the component lifecycle.
49+
50+
```ruby
51+
class FirstComponent < HyperComponent
52+
render do
53+
NextComponent() # ruby syntax requires either () or {} following the class name
54+
end
55+
end
56+
```
57+
58+
Note that you should never redefine the `new` or `initialize` methods, or call them directly. The equivalent of `initialize` is the `before_mount` method.
59+
60+
> The one exception to using `new` is within a spec to create a "headless" component in order to access its internal state and methods.
61+
62+
### Invoking Components
63+
64+
> Note: when invoking a component **you must have** a \(possibly empty\) parameter list or \(possibly empty\) block.
65+
> ```ruby
66+
MyCustomComponent() # ok
67+
MyCustomComponent {} # ok
68+
MyCustomComponent # <--- breaks
69+
> ```
70+
71+
## Multiple Components
72+
73+
So far, we've looked at how to write a single component to display data. Next let's examine how components are combined to build an application.
74+
75+
By building modular components that reuse other components with well-defined interfaces you can _separate the different concerns_ of your app. By building a custom component library for your application, you are expressing your UI in a way that best fits your domain.
76+
77+
### Composition Example
78+
79+
Let's create a simple Avatar component which shows a profile picture and username using the Facebook Graph API.
80+
81+
```ruby
82+
class Avatar < HyperComponent
83+
param :user_name
84+
85+
render(DIV) do
86+
# for each param a method with the same name is defined
87+
ProfilePic(user_name: user_name)
88+
ProfileLink(user_name: user_name)
89+
end
90+
end
91+
92+
class ProfilePic < HyperComponent
93+
param :user_name
94+
95+
# note that in Ruby blocks can use do...end or { ... }
96+
render { IMG(src: "https://graph.facebook.com/#{user_name}/picture") }
97+
end
98+
99+
class ProfileLink < HyperComponent
100+
param :user_name
101+
render do
102+
A(href: "https://www.facebook.com/#{user_name}") do
103+
user_name
104+
end
105+
end
106+
end
107+
```

docs/client-dsl/components.md renamed to docs/client-dsl/component-details-original.md

Lines changed: 33 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,27 @@
1-
# Hyperstack Component DSL
1+
## Component Ownership, Children, Keys, and Fragments
22

3-
The Hyperstack Component DSL is a set of class and instance methods that are used to describe React components and render the user-interface.
4-
5-
The DSL has the following major areas:
6-
7-
* The `HyperComponent` class
8-
* HTML DSL elements
9-
* Component Lifecycle Methods \(`before_mount`, `after_mount`, `after_update`\)
10-
* The `param` and `render` methods
11-
* Event handlers
12-
* Miscellaneous methods
13-
14-
## HyperComponent
15-
16-
By convention your Hyperstack Components will inherit from the `HyperComponent` class, which typically would look like this:
17-
18-
```ruby
19-
class HyperComponent
20-
# All component classes must include Hyperstack::Component
21-
include Hyperstack::Component
22-
# The Observable module adds state handling
23-
include Hyperstack::State::Observable
24-
# The following turns on the new style param accessor
25-
# i.e. param :foo is accessed by the foo method
26-
param_accessor_style :accessors
27-
end
28-
29-
# and is used like this:
30-
31-
class AnotherComponent < HyperComponent
32-
...
33-
end
34-
```
35-
36-
Having an Application wide HyperComponent class allows you to modify component behavior on an application basis, similar to the way Rails uses `ApplicationRecord` and `ApplicationController` classes.
37-
38-
## The `render` Callback
39-
40-
At a minimum every component class must define a `render` block which returns one or more child elements. Those children may in turn have an arbitrarily deep structure.
41-
42-
```ruby
43-
class Component < HyperComponent
44-
render do
45-
DIV { } # render an empty div
46-
end
47-
end
48-
```
49-
50-
To save a little typing you can also specify the top level element to be rendered:
51-
52-
```ruby
53-
class Component < HyperComponent
54-
render(DIV, class: 'my-special-class') do
55-
# everything will be rendered in a div
56-
end
57-
end
58-
```
59-
60-
To render a component, you reference its class name as a method call from another component. This creates a new instance, passes any parameters and proceeds with the component lifecycle.
61-
62-
```ruby
63-
class FirstComponent < HyperComponent
64-
render do
65-
NextComponent() # ruby syntax requires either () or {} following the class name
66-
end
67-
end
68-
```
69-
70-
Note that you should never redefine the `new` or `initialize` methods, or call them directly. The equivalent of `initialize` is the `before_mount` method.
71-
72-
> The one exception to using `new` is within a spec to create a "headless" component in order to access its internal state and methods.
73-
74-
### Invoking Components
75-
76-
> Note: when invoking a component **you must have** a \(possibly empty\) parameter list or \(possibly empty\) block.
77-
> ```ruby
78-
MyCustomComponent() # ok
79-
MyCustomComponent {} # ok
80-
MyCustomComponent # <--- breaks
81-
> ```
82-
83-
## Multiple Components
84-
85-
So far, we've looked at how to write a single component to display data. Next let's examine one of React's finest features: composability.
86-
87-
### Motivation: Separation of Concerns
88-
89-
By building modular components that reuse other components with well-defined interfaces, you get much of the same benefits that you get by using functions or classes. Specifically you can _separate the different concerns_ of your app however you please simply by building new components. By building a custom component library for your application, you are expressing your UI in a way that best fits your domain.
3+
### Ownership
904

91-
### Composition Example
5+
In the following example (copied from the previous section) instances of `Avatar` _own_ instances of `ProfilePic` and `ProfileLink`. In Hyperstack (like React), **an owner is the component that sets the `params` of other components**. More formally, if a component `X` is created in component `Y`'s `render` method, it is said that `X` is _owned by_ `Y`. As will be discussed later a component cannot mutate its `params` — they are always consistent with what its owner sets them to. This fundamental invariant leads to UIs that are guaranteed to be consistent.
926

93-
Let's create a simple Avatar component which shows a profile picture and username using the Facebook Graph API.
7+
It's important to draw a distinction between the owner-owned-by relationship and the parent-child relationship. The owner-owned-by relationship is specific to Hyperstack/React, while the parent-child relationship is simply the one you know and love from the DOM. In the example above, `Avatar` owns the `DIV`, `ProfilePic` and `ProfileLink` instances, and `DIV` is the **parent** \(but not owner\) of the `ProfilePic` and `ProfileLink` instances.
948

959
```ruby
9610
class Avatar < HyperComponent
9711
param :user_name
9812

99-
render(DIV) do
100-
# for each param a method with the same name is defined
101-
ProfilePic(user_name: user_name)
102-
ProfileLink(user_name: user_name)
13+
render do # this can be shortened to render(DIV) do - see the previous section
14+
DIV do
15+
ProfilePic(user_name: user_name) # belongs to Avatar, owned by DIV
16+
ProfileLink(user_name: user_name) # belongs to Avatar, owned by DIV
17+
end
10318
end
10419
end
10520

10621
class ProfilePic < HyperComponent
10722
param :user_name
108-
109-
render do
110-
IMG(src: "https://graph.facebook.com/#{user_name}/picture")
111-
end
23+
# note that in Ruby blocks can use do...end or { ... }
24+
render { IMG(src: "https://graph.facebook.com/#{user_name}/picture") }
11225
end
11326

11427
class ProfileLink < HyperComponent
@@ -121,38 +34,42 @@ class ProfileLink < HyperComponent
12134
end
12235
```
12336

124-
### Ownership
125-
126-
In the above example, instances of `Avatar` _own_ instances of `ProfilePic` and `ProfileLink`. In React, **an owner is the component that sets the `params` of other components**. More formally, if a component `X` is created in component `Y`'s `render` method, it is said that `X` is _owned by_ `Y`. As discussed earlier, a component cannot mutate its `params` — they are always consistent with what its owner sets them to. This fundamental invariant leads to UIs that are guaranteed to be consistent.
127-
128-
It's important to draw a distinction between the owner-ownee relationship and the parent-child relationship. The owner-ownee relationship is specific to React, while the parent-child relationship is simply the one you know and love from the DOM. In the example above, `Avatar` owns the `div`, `ProfilePic` and `ProfileLink` instances, and `div` is the **parent** \(but not owner\) of the `ProfilePic` and `ProfileLink` instances.
129-
13037
### Children
13138

132-
When you create a React component instance, you can include additional React components or JavaScript expressions between the opening and closing tags like this:
39+
Parent components may be provided a *block* (an anonymous Ruby function or callback) that the parent will use to generate its children. Inside
40+
the block can be abitrary Ruby code that can generate any number of children *Elements* that will be rendered by the parent.
13341

13442
```ruby
135-
Parent { Child() }
43+
DIV do
44+
# this DIV will have two children
45+
ProfilePic(user_name: user_name)
46+
ProfileLink(user_name: user_name)
47+
end
48+
# or
49+
A(href: "https://www.facebook.com/#{user_name}") do
50+
# the anchor tag will have a single child the user_name string
51+
user_name
52+
end
13653
```
13754

138-
`Parent` can iterate over its children by accessing its `children` method.
55+
Note that as well as calling other Components (such as ProfilePic) the child block may return a string that will used as the single child content of
56+
parent. If the child block has already generated a other components this final string will be ignored.
57+
13958

14059
### Child Reconciliation
14160

142-
**Reconciliation is the process by which React updates the DOM with each new render pass.** In general, children are reconciled according to the order in which they are rendered. For example, suppose we have the following render method displaying a list of items. On each pass the items will be completely re-rendered:
61+
**Reconciliation is the process by which the underlying React engine updates the DOM with each new render pass.** In general, children are reconciled according to the order in which they are rendered. For example, suppose we have the following render method displaying a list of items. On each pass the items will be regenerated and then merged into the DOM by React.
14362

14463
```ruby
14564
param :items
14665
render do
14766
items.each do |item|
148-
P do
149-
item[:text]
150-
end
67+
P { item[:text] }
15168
end
15269
end
15370
```
15471

155-
What if the first time items was `[{text: "foo"}, {text: "bar"}]`, and the second time items was `[{text: "bar"}]`? Intuitively, the paragraph `<p>foo</p>` was removed. Instead, React will reconcile the DOM by changing the text content of the first child and destroying the last child. React reconciles according to the _order_ of the children.
72+
What if the first time items was `[{text: "foo"}, {text: "bar"}]`, and the second time items was `[{text: "bar"}]`? Intuitively, the paragraph `<p>foo</p>` was removed. Instead, React will reconcile the DOM by changing the text content of the first child and destroying the last child. React reconciles according to the _order_ of the children.
15673

15774
### Dynamic Children
15875

@@ -171,8 +88,7 @@ The situation gets more complicated when the children are shuffled around \(as i
17188

17289
When React reconciles the keyed children, it will ensure that any child with `key` will be reordered \(instead of clobbered\) or destroyed \(instead of reused\).
17390

174-
The `key` should _always_ be supplied directly to the components in the array, not to the container HTML child of each component in the array:
175-
91+
> For best results the `key` is supplied at highest level possible.
17692
```ruby
17793
# WRONG!
17894
class ListItemWrapper < HyperComponent
@@ -193,7 +109,6 @@ class MyComponent < HyperComponent
193109
end
194110
end
195111
```
196-
197112
```ruby
198113
# CORRECT
199114
class ListItemWrapper < HyperComponent
@@ -215,7 +130,9 @@ class MyComponent < HyperComponent
215130
end
216131
```
217132

218-
> The `to_key` method: Any ruby object can be a key. Under the hood the HyperComponent DSL will call the object's `to_key` method which will respond with a unique value representing the object. For example if you pass an ActiveRecord model instance as a key, the result will be the database id of the model.
133+
### The `to_key` method
134+
135+
Any ruby object can be a key. Under the hood Hyperstack will call the object's `to_key` method which will respond with an appropriate unique value representing the object. For example if you pass an ActiveRecord model instance as a key, the result will be the database id of the model.
219136

220137
### The children and render methods
221138

0 commit comments

Comments
 (0)