Skip to content

Commit b0bed77

Browse files
committed
Merge branch 'edge' of github.com:hyperstack-org/hyperstack into edge
2 parents 167d5a3 + f8ea1c9 commit b0bed77

File tree

5 files changed

+380
-6
lines changed

5 files changed

+380
-6
lines changed

docs/dsl-client/hyper-component.md

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,6 +1430,154 @@ However it gets a little tricky if you are using the react-rails gem. Each vers
14301430
14311431
```
14321432

1433+
## Single Page Application CRUD example
1434+
1435+
Rails famously used scaffolding for Model CRUD (Create, Read, Update and Delete). There is no scaffolding in Hyperstack today, so here is an example which demonstrates how those simple Rails pages would work in Hyperstack.
1436+
1437+
This example uses components from the [Material UI](https://material-ui.com/) framework, but the principals would be similar for any framework (or just HTML elements).
1438+
1439+
In this example, we will have a table of users and the ability to add new users or edit a user from the list. As the user edits the values in the UserDialog, they will appear in the underlying table. You can avoid this behaviour if you choose by copying the values in the `before_mount` of the UserDialog, so you are not interacting with the model directly.
1440+
Firstly the table of users:
1441+
1442+
1443+
```ruby
1444+
class UserIndex < HyperComponent
1445+
render(DIV, class: 'mo-page') do
1446+
UserDialog(user: User.new) # this will render as a button to add users
1447+
Table do
1448+
TableHead do
1449+
TableRow do
1450+
TableCell { 'Name' }
1451+
TableCell { 'Gender' }
1452+
TableCell { 'Edit' }
1453+
end
1454+
end
1455+
TableBody do
1456+
user_rows
1457+
end
1458+
end
1459+
end
1460+
1461+
def user_rows
1462+
User.each do |user| # note User is a Hyperstack model (see later in the Isomorphic section)
1463+
TableRow do
1464+
TableCell { "#{user.first_name} #{user.last_name}" }
1465+
TableCell { user.is_female ? 'Female' : 'Male' }
1466+
# note the use of key so React knows which Component this refers to
1467+
# this is very important for performance and to ensure the component is used
1468+
TableCell { UserDialog(user: user, key: user.id) } # this will render as an edit button
1469+
end
1470+
end
1471+
end
1472+
end
1473+
```
1474+
1475+
Then we need the actual Dialog (Modal) component:
1476+
1477+
```ruby
1478+
class UserDialog < HyperComponent
1479+
param :user
1480+
1481+
before_mount do
1482+
@open = false
1483+
end
1484+
1485+
render do
1486+
if @open
1487+
render_dialog
1488+
else
1489+
edit_or_new_button.on(:click) { mutate @open = true }
1490+
end
1491+
end
1492+
1493+
def render_dialog
1494+
Dialog(open: @open, fullWidth: false) do
1495+
DialogTitle do
1496+
'User'
1497+
end
1498+
DialogContent do
1499+
content
1500+
error_messages if @User.errors.any?
1501+
end
1502+
DialogActions do
1503+
actions
1504+
end
1505+
end
1506+
end
1507+
1508+
def edit_or_new_button
1509+
if @User.new?
1510+
Fab(size: :small, color: :primary) { Icon { 'add' } }
1511+
else
1512+
Fab(size: :small, color: :secondary) { Icon { 'settings' } }
1513+
end
1514+
end
1515+
1516+
def content
1517+
FormGroup(row: true) do
1518+
# note .to_s to specifically cast to a String
1519+
TextField(label: 'First Name', value: @User.first_name.to_s).on(:change) do |e|
1520+
@User.first_name = e.target.value
1521+
end
1522+
TextField(label: 'Last Name', value: @User.last_name.to_s).on(:change) do |e|
1523+
@User.last_name = e.target.value
1524+
end
1525+
end
1526+
1527+
BR()
1528+
1529+
FormLabel(component: 'legend') { 'Gender' }
1530+
RadioGroup(row: true) do
1531+
FormControlLabel(label: 'Male',
1532+
control: Radio(value: false, checked: !is_checked(@User.is_female)).as_node.to_n)
1533+
FormControlLabel(label: 'Female',
1534+
control: Radio(value: true, checked: is_checked(@User.is_female)).as_node.to_n)
1535+
end.on(:change) do |e|
1536+
@User.is_female = e.target.value
1537+
end
1538+
end
1539+
1540+
def is_checked value
1541+
# we need a true or false and not an object
1542+
value ? true : false
1543+
end
1544+
1545+
def actions
1546+
Button { 'Cancel' }.on(:click) { cancel }
1547+
1548+
if @User.changed? && validate_content
1549+
Button(color: :primary, variant: :contained, disabled: (@User.saving? ? true : false)) do
1550+
'Save'
1551+
end.on(:click) { save }
1552+
end
1553+
end
1554+
1555+
def save
1556+
@User.save(validate: true).then do |result|
1557+
mutate @open = false if result[:success]
1558+
end
1559+
end
1560+
1561+
def cancel
1562+
@User.revert
1563+
mutate @open = false
1564+
end
1565+
1566+
def error_messages
1567+
@User.errors.full_messages.each do |message|
1568+
Typography(variant: :h6, color: :secondary) { message }
1569+
end
1570+
end
1571+
1572+
def validate_content
1573+
return false if @User.first_name.to_s.empty?
1574+
return false if @User.last_name.to_s.empty?
1575+
return false if @User.is_female.nil?
1576+
1577+
true
1578+
end
1579+
end
1580+
```
14331581

14341582
## Elements and Rendering
14351583

docs/dsl-client/hyper-store.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# Stores
22

3-
**Work in progress - ALPHA (docs and code)**
4-
**These docs are out of date**
3+
**These docs are out of date - please ignore this section until it is updated**
54

65
Hyperstack **Stores** are implemented in the **HyperStore Gem**.
76

docs/installation/upgrading.md

Lines changed: 211 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,214 @@
11
# Upgrading from legacy Hyperloop
22

3-
These instructions are subject to change as the project matures.
3+
This guide sets out to provide the steps necessary to move an existing project from legacy Hyperloop to Hyperstack. There are a number of changes which need to be considered.
44

5-
TBD
5+
## Summary of changes
6+
7+
+ Creating a new Hyperstack Rails application
8+
+ New Hyperstack gems
9+
+ Renamed folders
10+
+ Hyperstack configuration
11+
+ Hyperloop classes have been renamed Hyperstack
12+
+ There is a new concept of a base `HyperComponent` and `HyperStore` base class
13+
+ State syntax has changed
14+
+ Param syntax has changed
15+
+ The Router DSL has changed slightly
16+
17+
## Creating a new Hyperstack Rails application
18+
19+
In Hyperstack we are using Rails templates to create new applications.
20+
21+
+ Follow these instructions: https://github.com/hyperstack-org/hyperstack/tree/edge/install
22+
+ See the template for an understanding of the installation steps: https://github.com/hyperstack-org/hyperstack/blob/edge/install/rails-webpacker.rb
23+
24+
**If you are not upgrading an existing Hyoperloop application, you do not need to follow the rest of these instructions.**
25+
26+
## Hyperstack gem
27+
28+
Hyperstack (with Rails) requires just one Gem:
29+
30+
```ruby
31+
# gem 'webpacker' # if you are using webpacker
32+
gem 'rails-hyperstack'
33+
```
34+
35+
Delete all Hyperloop gems from your gemfile and do a `bundle update`.
36+
37+
## Renamed folders
38+
39+
+ `app/hyperloop` has become `app/hyperstack`
40+
+ The sub-folder structure has not changed (components, models, stores, etc)
41+
42+
## Hyperstack configuration
43+
44+
+ `config/initializers/hyperloop.rb` has been renamed `config/initializers/hyperstack.rb`
45+
46+
The configuration initialiser has changed a little. Please see this page for details: https://github.com/hyperstack-org/hyperstack/blob/edge/docs/installation/config.md
47+
48+
## Hyperloop classes have been renamed Hyperstack
49+
50+
In all cases, `Hyperloop` has been replaced with `Hyperstack`. For example:
51+
52+
In Hyperloop:
53+
54+
```ruby
55+
Hyperloop::Application.acting_user_id
56+
```
57+
In Hyperstack becomes:
58+
59+
```ruby
60+
Hyperstack::Application.acting_user_id
61+
```
62+
63+
The simplest way to implement this change is a global search and replace in your project.
64+
65+
## There is a new concept of a base HyperComponent and HyperStore base class
66+
67+
In Hyperloop, all Components and Stores inherited from a base `Hyperloop::Component` class. In HyperStack (following the new Rails convention), we do not provide the base class but encourage you to create your own. This is very useful for containing methods that all your Components share.
68+
69+
To implement this change, you need to create your HyperComponent class:
70+
71+
```ruby
72+
class HyperComponent
73+
include Hyperstack::Component
74+
include Hyperstack::State::Observable # if you are using state
75+
include Hyperstack::Router::Helpers # if you are using the router
76+
77+
def some_shared_method
78+
# a helper method that all your Component might need
79+
end
80+
end
81+
```
82+
83+
Then you need to do a search and replace on all your `Hyperloop::Component` classes and replace them with `HyperComponent`. For example:
84+
85+
In Hyperloop:
86+
87+
```ruby
88+
class MyComp < Hyperloop::Component
89+
render do
90+
...
91+
end
92+
end
93+
```
94+
95+
In Hyperstack becomes:
96+
97+
```ruby
98+
class MyComp < HyperComponent
99+
render do
100+
...
101+
end
102+
end
103+
```
104+
105+
`HyperComponent` is explained here: https://github.com/hyperstack-org/hyperstack/blob/edge/docs/dsl-client/hyper-component.md#hypercomponent
106+
107+
108+
**The same is true for `Hyperloop::Store` to `HyperStore`**.
109+
110+
You will need to create a `HyperStore` class and make the same changes as above.
111+
112+
Note that in Hyperstack, any ruby class can be a store by merely including the `include Hyperstack::State::Observable` mixin.
113+
114+
For example:
115+
116+
```ruby
117+
class StoresAreUs
118+
include Hyperstack::State::Observable
119+
120+
def store_something thing
121+
mutate @thing = thing # note the new mutate syntax
122+
end
123+
end
124+
```
125+
126+
## State syntax has changed
127+
128+
In Hyperloop you mutated state like this:
129+
130+
```ruby
131+
mutate.something true
132+
```
133+
134+
In Hyperstack becomes:
135+
136+
```ruby
137+
mutate @something = true
138+
```
139+
140+
You also use reference in a different way:
141+
142+
In Hyperloop:
143+
144+
```ruby
145+
H1 { 'Yay' } if state.something
146+
```
147+
148+
In Hyperstack becomes:
149+
150+
```ruby
151+
H1 { 'Yay' } if @something
152+
```
153+
154+
There are several advantages to this new approach:
155+
156+
+ It is significantly faster (ask Mitch)
157+
+ It feels more natural to think about state variables as normal instance variables
158+
+ You only use the `mutate` method when you want React to re-render based on the change to state. This gives you more control.
159+
+ You can string mutations together. For example:
160+
161+
```ruby
162+
mutate @something = true, @amount = 100, @living = :good
163+
```
164+
165+
You can read more about state here: https://github.com/hyperstack-org/hyperstack/blob/edge/docs/dsl-client/hyper-component.md#state
166+
167+
## Param syntax has changed
168+
169+
The syntax for using params has changed:
170+
171+
In Hyperloop:
172+
173+
```ruby
174+
class SayHello < Hyper::Component
175+
param :first_name
176+
177+
render do
178+
H1 { "Hello #{params.first_name}" }
179+
end
180+
```
181+
182+
In Hyperstack becomes:
183+
184+
```ruby
185+
class SayHello < HyperComponent
186+
param :first_name
187+
188+
render do
189+
H1 { "Hello #{@FirstName}" } # Note the conversion of snake_case to CamelCase
190+
end
191+
```
192+
193+
This change has been made to underline that params are immutable. (Though this is not true for Models, but they get the same CamelCase treatment).
194+
195+
You can read more about this here: https://github.com/hyperstack-org/hyperstack/blob/edge/docs/dsl-client/hyper-component.md#params
196+
197+
## The Router DSL has changed slightly
198+
199+
Routers are now normal Components with a render method.
200+
201+
A Hyperstack router looks like this:
202+
203+
```ruby
204+
class MainFrame < HyperComponent
205+
include Hyperstack::Router # note the inclusion of the Router mixin
206+
207+
render(DIV) do # note the render method
208+
Switch do
209+
Route('/', exact: true, mounts: HomeIndex)
210+
Route('/app', exact: true, mounts: AppIndex)
211+
end
212+
end
213+
end
214+
```

0 commit comments

Comments
 (0)