Skip to content

Commit 80e062e

Browse files
author
Nils Henning
committed
document new require and optional mechanism
1 parent 00462f5 commit 80e062e

File tree

4 files changed

+188
-84
lines changed

4 files changed

+188
-84
lines changed

.byebug_history

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
continue
2+
page.html
3+
continue
24
save_screenshot
35
continue
46
save_screenshot

app/lib/matestack/ui/core/properties.rb

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,20 @@ def initialize(model=nil, options={})
2626
module ClassMethods
2727
# define optinoal properties for custom components with `optional :foo, :bar`
2828
def optional(*properties)
29-
properties.each { |property| optional_properties.push(property) }
29+
add_properties_to_list(optional_properties, properties)
3030
end
3131

3232
# define required properties for custom components with `requires :title, :foo, :bar`
3333
def requires(*properties)
34+
add_properties_to_list(requires_properties, properties)
35+
end
36+
37+
def add_properties_to_list(list, properties)
3438
properties.each do |property|
3539
if property.is_a? Hash
36-
property.each { |tmp_property| requires_properties.push(tmp_property) }
40+
property.each { |tmp_property| list.push(tmp_property) }
3741
else
38-
requires_properties.push(property)
42+
list.push(property)
3943
end
4044
end
4145
end
@@ -53,6 +57,11 @@ def requires_properties
5357

5458
def optional_hooks
5559
self.class.optional_properties.compact.each do |prop|
60+
if prop.is_a? Array
61+
hash = prop.flatten
62+
options[hash.last[:as]] = options[hash.first]
63+
prop = hash.last[:as]
64+
end
5665
raise PropertyOverwritingExistingMethodException, "Optional property #{prop} would overwrite already defined instance method for #{self.class}" if self.respond_to? prop
5766
send(:define_singleton_method, prop) do
5867
options[prop]

docs/concepts/component.md

Lines changed: 109 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -40,94 +40,131 @@ See below for an overview of the various possibilities Matestack provides for co
4040

4141
By passing options to a component, they get very flexible and can take various input, e.g. *i18n* strings or *instance variables*!
4242

43-
#### Passing options as a hash
43+
#### Define optional and required properties
4444

45-
As before, create a component in it's respective file, in this case `app/matestack/components/some/component.rb`:
45+
Matestack Components give you the option to define required and optional properties for a component. It creates helpers for these properties automatically.
46+
47+
##### Required
48+
49+
Required properties are required for your component to work, like the name suggests. If at least one required property is missing a `Matestack::Ui::Core::Properties::PropertyMissingException` is raised.
50+
Declare your required properties by calling `requires` as follows:
4651

4752
```ruby
48-
class Components::Some::Component < Matestack::Ui::StaticComponent
53+
class SomeComponent < Matestack::Ui::StaticComponent
4954

50-
def response
51-
components {
52-
div id: "my-component" do
53-
plain "I'm a static component and got some option: #{@options[:some_option]} and some other option: #{@options[:some_other][:option]}"
54-
end
55-
}
56-
end
55+
requires :some_property, :some_other
5756

5857
end
5958
```
6059

61-
On the example page, directly pass the options to the static component as shown below:
60+
You then can use these properties simply by calling the provided helper method, which is generated for you. The helper method name corresponds to the passed property name.
6261

6362
```ruby
64-
class Pages::ExamplePage < Matestack::Ui::Page
63+
class SomeComponent < Matestack::Ui::StaticComponent
6564

66-
def prepare
67-
@hello = "hello!"
68-
end
65+
required :some_property, :some_other
6966

7067
def response
71-
components {
72-
div id: "div-on-page" do
73-
custom_some_component some_option: @hello, some_other: { option: "world!" }
74-
end
75-
}
68+
# display some_property plain inside a div and some_other property inside a paragraph beneath it
69+
div do
70+
plain some_property
71+
end
72+
paragraph text: some_other
7673
end
7774

7875
end
7976
```
8077

81-
The outcome is quite as expected:
78+
##### Optional
8279

83-
```html
84-
<div id="div-on-page">
85-
<div id="my-component">
86-
I'm a static component and got some option: hello! and some other option: world!
87-
</div>
88-
</div>
80+
To define optional attributes you can use the same syntax as `requires`. Just use `optional` instead of `requires`. Optional attributes are optional and not validated for presence like required attributes.
81+
82+
```ruby
83+
class SomeComponent < Matestack::Ui::StaticComponent
84+
85+
optional :optional_property, :other_optional_property # optional properties could be empty
86+
87+
def response
88+
# display optional_property plain inside a div and other_optional_property property inside a paragraph beneath it
89+
div do
90+
plain optional_property
91+
end
92+
paragraph text: other_optional_property
93+
end
94+
95+
end
8996
```
9097

91-
#### Introducing required options
98+
##### Passing properties to components
9299

93-
This feature is pretty straightforward - just declare the options as a required key in the component config!
100+
Pass the properties as a hash directly to the component when calling it. You can pass any object you like and use it in the component with the helper.
94101

102+
A custom component
95103
```ruby
96-
class Components::SpecialComponent < Matestack::Ui::StaticComponent
104+
class SomeComponent < Matestack::Ui::StaticComponent
97105

98-
requires :some_option
106+
required :some_option,
107+
optional :some_other # optional properties could be empty
99108

100109
def response
101-
components {
102-
div id: "my-component" do
103-
plain "I'm a static component and got some option: #{@options[:some_option]} and some other option: #{@options[:some_other][:option]}"
104-
end
105-
}
110+
div do
111+
plain some_option
112+
end
113+
if some_other.present?
114+
paragraph text: some_other[:option]
115+
end
106116
end
107117

108118
end
109119
```
110120

111-
Notice that this example *does not* pass the required option to the component on the example page, provoking an error message:
112-
121+
Use it in the example page and pass in the properties as a hash
113122
```ruby
114123
class Pages::ExamplePage < Matestack::Ui::Page
115124

125+
def prepare
126+
@hello = "hello!"
127+
end
128+
116129
def response
117130
components {
118131
div id: "div-on-page" do
119-
custom_specialComponent some_other: { option: "world!" }
132+
custom_some_component some_option: @hello, some_other: { option: "world!" }
120133
end
121134
}
122135
end
123136

124137
end
125138
```
126139

127-
The error message looks like this:
140+
The outcome is quite as expected:
128141

129142
```html
130-
"div > custom_specialComponent > required key 'some_option' is missing"
143+
<div id="div-on-page">
144+
<div>
145+
hello!
146+
</div>
147+
<p>world!</p>
148+
</div>
149+
```
150+
151+
##### Alias properties
152+
153+
Matestack tries to prevent overriding existing methods while creating helpers. If you pass a property with a name that matches any instance method of your component matestack will raise a `Matestack::Ui::Core::Properties::PropertyOverwritingExistingMethodException`.
154+
To use property names that would raise this exception, simply provide an alias name with the `as:` option. You can then use the alias accordingly.
155+
156+
```ruby
157+
class SomeComponent < Matestack::Ui::StaticComponent
158+
requires :foo, :bar, method: { as: :my_method }
159+
optional response: { as: :my_response }
160+
161+
def response
162+
div do
163+
plain "#{foo} - #{bar} - #{my_method}" # string concatenation of properties foo, bar, and method aliased as my_method
164+
end
165+
paragraph my_response if my_response.present? # response property aliased as my_response inside a paragraph if it is present
166+
end
167+
end
131168
```
132169

133170
#### Options validation
@@ -145,18 +182,18 @@ Define the slots within the component file as shown below:
145182
```ruby
146183
class Components::Some::Component < Matestack::Ui::StaticComponent
147184

185+
requires :my_first_slot, :my_second_slot
186+
148187
def prepare
149188
@foo = "foo from component"
150189
end
151190

152191
def response
153-
components {
154-
div id: "my-component" do
155-
slot @options[:my_first_slot]
156-
br
157-
slot @options[:my_second_slot]
158-
end
159-
}
192+
div id: "my-component" do
193+
slot my_first_slot
194+
br
195+
slot my_second_slot
196+
end
160197
end
161198

162199
end
@@ -172,11 +209,9 @@ class Pages::ExamplePage < Matestack::Ui::Page
172209
end
173210

174211
def response
175-
components {
176-
div do
177-
custom_some_component my_first_slot: my_simple_slot, my_second_slot: my_second_simple_slot
178-
end
179-
}
212+
div do
213+
custom_some_component my_first_slot: my_simple_slot, my_second_slot: my_second_simple_slot
214+
end
180215
end
181216

182217
def my_simple_slot
@@ -221,20 +256,21 @@ To use *component instance scope slots*, first define slots within a static comp
221256
```ruby
222257
class Components::Other::Component < Matestack::Ui::StaticComponent
223258

259+
requires :my_slot_from_component
260+
requires :my_slot_from_page
261+
224262
def prepare
225263
@foo = "foo from other component"
226264
end
227265

228266
def response
229-
components {
230-
div id: "my-other-component" do
231-
slot @options[:slots][:my_slot_from_component]
232-
br
233-
slot @options[:slots][:my_slot_from_page]
234-
br
235-
plain @foo
236-
end
237-
}
267+
div id: "my-other-component" do
268+
slot my_slot_from_component
269+
br
270+
slot my_slot_from_page
271+
br
272+
plain @foo
273+
end
238274
end
239275

240276
end
@@ -245,19 +281,16 @@ and also in some component:
245281
```ruby
246282
class Components::Some::Component < Matestack::Ui::StaticComponent
247283

284+
requires :my_slot_from_page
285+
248286
def prepare
249287
@foo = "foo from component"
250288
end
251289

252290
def response
253-
components {
254-
div id: "my-component" do
255-
custom_other_component slots: {
256-
my_slot_from_component: my_slot_from_component,
257-
my_slot_from_page: @options[:my_slot_from_page]
258-
}
259-
end
260-
}
291+
div id: "my-component" do
292+
custom_other_component my_slot_from_component: my_slot_from_component, my_slot_from_page: my_slot_from_page
293+
end
261294
end
262295

263296
def my_slot_from_component
@@ -281,11 +314,9 @@ class Pages::ExamplePage < Matestack::Ui::Page
281314
end
282315

283316
def response
284-
components {
285-
div id: "page-div" do
286-
custom_some_component my_slot_from_page: my_slot_from_page
287-
end
288-
}
317+
div id: "page-div" do
318+
custom_some_component my_slot_from_page: my_slot_from_page
319+
end
289320
end
290321

291322
def my_slot_from_page
@@ -329,11 +360,9 @@ Components can yield a block with access to scope, where a block is defined. Thi
329360
class Components::Some::Component < Matestack::Ui::StaticComponent
330361

331362
def response
332-
components {
333-
div id: "my-component" do
334-
yield_components
335-
end
336-
}
363+
div id: "my-component" do
364+
yield_components
365+
end
337366
end
338367

339368
end

0 commit comments

Comments
 (0)