Skip to content

Commit acef186

Browse files
jonasjabarigitbook-bot
authored andcommitted
GitBook: [#4] No subject
1 parent afe194f commit acef186

12 files changed

+410
-550
lines changed

docs/SUMMARY.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@
4040
## Custom Reactivity implemented in Vue.js <a href="#custom-reactivity" id="custom-reactivity"></a>
4141

4242
* [Custom Vue.js Components](custom-reactivity/custom-vue-js-components.md)
43-
* [Third party Vue.js Components \[WIP\]](custom-reactivity/third-party-vue.js-components.md)
44-
* [Matestack's Vue.js APIs \[WIP\]](custom-reactivity/matestacks-vue-js-apis.md)
45-
* [Matestack's Vuex API \[WIP\]](custom-reactivity/matestacks-vuex-api.md)
43+
* [Third party Vue.js Components \[WIP\]](custom-reactivity/third-party-vue.js-components-wip.md)
44+
* [Matestack's Vue.js APIs \[WIP\]](custom-reactivity/matestacks-vue.js-apis-wip.md)
45+
* [Matestack's Vuex API \[WIP\]](custom-reactivity/matestacks-vuex-api-wip.md)
4646

4747
## Integrations
4848

docs/built-in-reactivity/partial-ui-updates/async-component-api.md

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Please be aware that, if not configured otherwise, the `async` core component do
88

99
The `async` core component accepts the following parameters:
1010

11-
### ID - required
11+
### ID - required
1212

1313
The `async` component needs an ID in order to resolve the correct content on an async HTTP request
1414

@@ -32,13 +32,13 @@ end
3232

3333
**Note:** The `rerender_on` option lets you rerender parts of your UI asynchronously. But please consider that, if not configured differently, it
3434

35-
a\) is **not** _lazily loaded_ and
35+
a) is **not** _lazily loaded_ and
3636

37-
b\) and does get displayed on initial pageload
37+
b) and does get displayed on initial pageload
3838

3939
by default.
4040

41-
Lazy \(or defered\) loading can be configured like shown [here](async-component-api.md#defer).
41+
Lazy (or defered) loading can be configured like shown [here](async-component-api.md#defer).
4242

4343
You can pass in multiple, comma-separated events on which the component should rerender.
4444

@@ -127,43 +127,43 @@ During async rendering a `loading` class will automatically be applied, which ca
127127

128128
## Examples
129129

130-
See some common use cases below:
130+
### Deferring content
131131

132-
### Rerender on event
133-
134-
On our example page, we wrap a simple timestamp in an async component and tell it to rerender when the event `my_event` gets triggered.
132+
You can either configure an `async` component to request its content directly after the page load or to delay the request for a given amount of time after the page load. `:defer` expects either a boolean or a integer representing the delay time in milliseconds. If `:defer` is set to `false` the `async` component will be rendered on page load and not deferred. If set to `true` it will request its content directly after the page load.
135133

136134
```ruby
137-
class ExamplePage < Matestack::Ui::Page
138-
139-
def response
140-
async rerender_on: 'my_event', id: "some-unique-id" do
141-
div id: 'my-div' do
142-
plain "#{DateTime.now.strftime('%Q')}"
143-
end
144-
end
135+
def response
136+
async id: 'deferred-async', defer: true do
137+
plain 'Some content rendered after page is loaded.'
145138
end
139+
end
140+
```
146141

142+
The above `async` component will be rendered asynchronously after page load.
143+
144+
```ruby
145+
def response
146+
async id: 'delayed-deferred-async', defer: 500 do
147+
plain 'Some delayed deferred content'
148+
end
147149
end
148150
```
149151

150-
Not surprisingly, the timestamp gets updated after our event was fired!
152+
Specifying `defer: 500` will delay the asynchronous request after page load of the `async` component for 300ms and render the content afterwards.
151153

152-
### Deferred loading
154+
### Rerendering content
153155

154-
On our example page, we wrap our `async` event around a placeholder for the event message.
156+
The `async` leverages the event hub and can react to emitted events. If it receives one or more of the with `:rerender_on` specified events it will asynchronously request a rerender of its content. The response will only include the rerendered html of the `async` component which then replaces the current content of the `async`. If you specify multiple events in `:rerender_on` they need to be seperated by a comma.
155157

156158
```ruby
157-
class ExamplePage < Matestack::Ui::Page
158-
159-
def response
160-
async defer: true, id: "some-unique-id" do
161-
div id: 'my-div' do
162-
plain 'I will be requested within a separate GET request right after initial page load is done'
163-
end
164-
end
159+
def response
160+
async id: 'rerendering-async', rerender_on: 'update-time' do
161+
paragraph DateTime.now
162+
end
163+
onclick emit: 'update-time' do
164+
button text: 'Update time'
165165
end
166-
167166
end
168167
```
169168

169+
The above snippet renders a paragraph with the current time and a button "Update time" on page load. If the button is clicked a _update-time_ event is emitted. The `async` component wrapping the paragraph receives the event and reacts to it by requesting its rerendered content from the server and replacing its content with the received html. In this case it will rerender after button click and show the updated time.

docs/built-in-reactivity/partial-ui-updates/cable-component-api.md

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,238 @@ ActionCable.server.broadcast("matestack_ui_core", {
185185

186186
`data` can also be an Array of ID-strings.
187187

188+
## Examples
189+
190+
191+
192+
###
193+
194+
Imagine a list of Todo Items and a above that list a form to create new Todos, implemented like this:
195+
196+
```ruby
197+
class MyPage < Matestack::Ui::Page
198+
199+
def response
200+
matestack_form method: :post, path: create_action_rails_route_path do
201+
form_input key: :title, type: :text
202+
button "submit"
203+
end
204+
205+
Todo.all.each do |instance|
206+
TodoComponent.call(todo: instance)
207+
end
208+
end
209+
210+
end
211+
```
212+
213+
```ruby
214+
class TodoComponent < Matestack::Ui::Component
215+
216+
required :todo
217+
218+
def response
219+
div id: "todo-#{context.todo.id}", class: "row" do
220+
div class: "col" do
221+
plain context.todo.title
222+
end
223+
end
224+
end
225+
226+
end
227+
```
228+
229+
After form submission, the form resets itself dynamically, but the list will not get updated with the new todo instance. You can now decide, if you want to use `async` or `cable` in order to implement that reactivity. `async` could react to an event emitted by the `form` and simply rerender the whole list without any further implementation. Wrapping the list in a correctly configured `async` component would be enough!
230+
231+
But in this case, we do not want to rerender the whole list every time we submitted the form, because - let's say - the list will be quite long and rerendering the whole list would be getting slow. We only want to add new items to the current DOM without touching the rest of the list. The `cable` component enables you to do exactly this. The principle behind it: After form submission the new component is rendered on the serverside and than pushed to the clientside via ActionCable. The `cable` component receives this event and will than - depending on your configuration - append or prepend the current list on the UI. Implementation would look like this:
232+
233+
234+
235+
### Appending elements
236+
237+
_adding elements on the bottom of the list_
238+
239+
```ruby
240+
class MyPage < Matestack::Ui::Page
241+
242+
def response
243+
matestack_form method: :post, path: create_action_rails_route_path do
244+
form_input key: :title, type: :text
245+
button "submit"
246+
end
247+
248+
cable id: "my-cable-list", append_on: "new_todo_created" do
249+
Todo.all.each do |instance|
250+
TodoComponent.call(todo: instance)
251+
end
252+
end
253+
end
254+
255+
end
256+
```
257+
258+
and on your controller:
259+
260+
```ruby
261+
def create
262+
@todo = Todo.create(todo_params)
263+
264+
unless @todo.errors.any?
265+
ActionCable.server.broadcast("matestack_ui_core", {
266+
event: "new_todo_created",
267+
data: TodoComponent.call(todo: @todo)
268+
})
269+
# respond to the form POST request (needs to be last)
270+
render json: { }, status: :created
271+
end
272+
end
273+
```
274+
275+
Please notice that we recommend to use a component for each list item. With a component for each item it is possible to easily render a new list item within the `create` action and push it to the client. But it is possible to also use another component or a html string. Any given html will be appended to the list.
276+
277+
### Prepending elements
278+
279+
_adding elements on the top of the list_
280+
281+
Prepending works pretty much the same as appending element, just configure your `cable` component like this:
282+
283+
```ruby
284+
cable id: "my-cable-list", prepend_on: "new_todo_created" do
285+
Todo.all.each do |instance|
286+
TodoComponent.call(todo: instance)
287+
end
288+
end
289+
```
290+
291+
### Updating elements
292+
293+
_updating existing elements within the list_
294+
295+
Now imagine you want to update elements in your list without browser reload because somewhere else the title of a todo instance was changed. You could use `async` for this as well. Esspecially because `async` can react to serverside events pushed via ActionCable as well. But again: `async` would rerender the whole list... and in our usecase we do not want to this. We only want to update a specific element of the list. Luckily the implementation for this features does not differ from the above explained ones!
296+
297+
Imagine somewhere else the specific todo was updated via a form targeting the following controller action:
298+
299+
```ruby
300+
def update
301+
@todo = Todo.find(params[:id])
302+
@todo.update(todo_params)
303+
304+
unless @todo.errors.any?
305+
ActionCable.server.broadcast("matestack_ui_core", {
306+
event: "todo_updated",
307+
data: TodoComponent.call(todo: @todo)
308+
})
309+
# respond to the form PUT request (needs to be last)
310+
render json: { }, status: :ok
311+
end
312+
end
313+
```
314+
315+
Again, the controller action renders a new version of the component and pushes that to the clientside. Nothing changed here! We only need to tell the `cable` component to react properly to that event:
316+
317+
```ruby
318+
cable id: "my-cable-list", update_on: "todo_updated" do
319+
Todo.all.each do |instance|
320+
TodoComponent.call(todo: instance)
321+
end
322+
end
323+
```
324+
325+
Please notice that it is mandatory to have a unique ID on the root element of each list item. The `cable` component will use the ID found in the root element of the pushed component in order to figure out, which element of the current list should be updated. In our example above we used `div id: "todo-#{todo.id}"` as the root element of our `todo_component` used for each element in the list.
326+
327+
### Removing elements
328+
329+
_removing existing elements within the list_
330+
331+
Well, of course we want to be able to remove elements from that list without rerendering the whole list, as `async` would do. The good thing: We can tell the `cable` component to delete elements by ID:
332+
333+
```ruby
334+
cable id: "my-cable-list", delete_on: "todo_deleted" do
335+
Todo.all.each do |instance|
336+
TodoComponent.call(todo: instance)
337+
end
338+
end
339+
```
340+
341+
Imagine somewhere else the following destroy controller action was targeted:
342+
343+
```ruby
344+
def destroy
345+
@todo = Todo.find(params[:id])
346+
347+
if @todo.destroy
348+
ActionCable.server.broadcast("matestack_ui_core", {
349+
event: "todo_deleted",
350+
data: "todo-#{params[:id]}"
351+
})
352+
# respond to the DELETE request (needs to be last)
353+
render json: { }, status: :deleted
354+
end
355+
end
356+
```
357+
358+
After deleting the todo instance, the controller action pushes an event via ActionCable, now including just the ID of the element which should be removed. Notice that this ID have to match the ID used on the root element of the component. In our example above we used `div id: "todo-#{todo.id}"` as the root element of our `todo_component` used for each element in the list.
359+
360+
### Replacing the whole component body
361+
362+
Now imagine a context in which the whole `cable` component body should be updated rather than just adding/updating/deleting specific elements of a list. In an online shop app this could be the shopping cart component rendered on the top right. When adding a product to the cart, you might want to update the shopping cart component in order to display the new amount of already included products.
363+
364+
The component may look like this:
365+
366+
```ruby
367+
class ShoppingCart < Matestack::Ui::Component
368+
369+
def response
370+
div id: "shopping-cart", class: "some-fancy-styling" do
371+
icon "some-shopping-cart-icon"
372+
span count, class: "some-badge"
373+
transition path: shopping_cart_path do
374+
button "to my cart"
375+
end
376+
end
377+
end
378+
379+
def count
380+
# some logic returning the amount of products in the users cart (saved on serverside)
381+
current_user.cart.products_count
382+
end
383+
384+
end
385+
```
386+
387+
Imagine somewhere else the following controller action was targeted when adding a product to the cart:
388+
389+
```ruby
390+
def add_to_cart
391+
@product = Product.find(params[:id])
392+
current_user.cart.add(@product) #some logic adding the product to the users cart (saved on serverside)
393+
394+
ActionCable.server.broadcast("matestack_ui_core", {
395+
event: "shopping_cart_updated",
396+
data: ShoppingCart.call()
397+
})
398+
render json: { }, status: :ok
399+
end
400+
```
401+
402+
and on your UI class (probably your app class):
403+
404+
```ruby
405+
cable id: "shopping-cart", replace_on: "shopping_cart_updated" do
406+
ShoppingCart.call()
407+
end
408+
```
409+
410+
### Event data as Array
411+
412+
All above shown examples demonstrated how to push a single component or ID to the `cable` component. In all usecases it's also possble to provide an Array of components/ID-strings, e.g.:
413+
414+
```ruby
415+
ActionCable.server.broadcast("matestack_ui_core", {
416+
event: "todo_updated",
417+
data: [
418+
ShoppingCart.call(todo: @todo1),
419+
ShoppingCart.call(todo: @todo2)
420+
]
421+
})
422+
```

0 commit comments

Comments
 (0)