|
1 | 1 | # Isolated
|
2 | 2 |
|
3 |
| - |
| 3 | +Matestacks concept of isolated components has a lot in common with `async` components. Isolated components can be deferred or asynchronously rerendered like `async` components. In the difference to `async` components, isolated components are resolved completetly independent from the rest of the ui. If an isolated component gets rerendered or loaded matestack will directly render this component without touching the app or page. With `async` matestack searches in an app or page which component needs to be rerendered. Therefore executing parts of an app and page whereas isolated components don't. |
| 4 | + |
| 5 | +Isolated components can not be called or used with a block like `async`, instead you need to create a component inheriting from `Matestack::Ui::IsolatedComponent`. Creation of the custom component works similar to other components, except you need to implement an `authorized?` method. As said above isolated components are completly independent and could be called directly via a url, therefore they need custom authorization. More about that later. |
| 6 | + |
| 7 | +Isolated components are perfectly when you have long runnning, complex database queries or business logic which concludes to slow page loads. Use an isolated component with the `:defer` option to keep your page loads fast and present the result to the user asynchronously. |
| 8 | + |
| 9 | +## Usage |
| 10 | + |
| 11 | +To create an isolated component you need to create a component which inherits from `Matestack::Ui::IsolatedComponent`. Implementing your component is straight forward. As always you implement a `response` method which defines what get's rendered. |
| 12 | + |
| 13 | +```ruby |
| 14 | +class CurrentTime < IsolatedComponent |
| 15 | + |
| 16 | + def response |
| 17 | + div class: 'time' do |
| 18 | + paragraph text: Time.now |
| 19 | + end |
| 20 | + end |
| 21 | + |
| 22 | + def authorized? |
| 23 | + true |
| 24 | + end |
| 25 | + |
| 26 | +end |
| 27 | +``` |
| 28 | + |
| 29 | +Register it like a usual component. |
| 30 | + |
| 31 | +```ruby |
| 32 | +module ComponentsRegistry |
| 33 | + Matestack::Ui::Core::Component::Registry.register_components( |
| 34 | + current_time: CurrentTime |
| 35 | + ) |
| 36 | +``` |
| 37 | + |
| 38 | +And use it with the `:defer` or `:rerender_on` options which work the same on `async` components. |
| 39 | + |
| 40 | +```ruby |
| 41 | +def response |
| 42 | + current_time defer: 1000, rerender_on: 'update-time' |
| 43 | +end |
| 44 | +``` |
| 45 | + |
| 46 | +### Deferred loading |
| 47 | + |
| 48 | +You can configure your isolated 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 instead of being rendered with the page. `:defer` expects either a boolean or a integer representing the delay time in milliseconds. If `:defer` is set to `false` your isolated component will be rendered on page load and not deferred. If set to `true` it will request its content directly after the page load. |
| 49 | + |
| 50 | +```ruby |
| 51 | +def response |
| 52 | + current_time defer: true |
| 53 | +end |
| 54 | +``` |
| 55 | + |
| 56 | +The above call to your isolated component will be skipped on page load and the component will request its content asynchronously directly after the page is loaded. |
| 57 | + |
| 58 | +```ruby |
| 59 | +def response |
| 60 | + current_time defer: 500 |
| 61 | +end |
| 62 | +``` |
| 63 | + |
| 64 | +This will load your isolated component 500ms after the page is loaded. |
| 65 | + |
| 66 | +### Rerendering content |
| 67 | + |
| 68 | +Isolated component leverage the event hub and can react to emitted events. If they receive one or more of the with `:rerender_on` specified events they will asynchronously request a rerender of their content. The server will only render the isolated component, not touching any of the apps or pages. The response will only include the rerendered html of the isolated component which then swaps out its current content with the response. If you specify multiple events in `:rerender_on` they need to be seperated by a comma. |
| 69 | + |
| 70 | +```ruby |
| 71 | +def response |
| 72 | + current_time rerender_on: 'update-time' |
| 73 | + onclick emit: 'update-time' do |
| 74 | + button text: 'Update time' |
| 75 | + end |
| 76 | +end |
| 77 | +``` |
| 78 | + |
| 79 | +The above snippet renders our `current_time` isolated component and a button "Update time" on page load. If the button is clicked a _update-time_ event is emitted. Our isolated component 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. |
| 80 | + |
| 81 | +Remember that you can use ActionCable to emit events on the serverside. |
| 82 | + |
| 83 | +### Authorization |
| 84 | + |
| 85 | +When asynchronously rendering isolated components, these HTTP calls are actually |
| 86 | +processed by the controller action responsible for the corresponding page rendering. |
| 87 | +One might think, that the optional authorization and authentication rules of that |
| 88 | +controller action should therefore be enough for securing isolated components rendering. |
| 89 | + |
| 90 | +But that's not true. It would be possible to hijack public controller actions without |
| 91 | +any authorization in place and request isolated components which are only meant to be |
| 92 | +rendered within a secured context. |
| 93 | + |
| 94 | +That's why we enforce the usage of the `authorized?` method to make sure, all isolated |
| 95 | +components take care of their authorization themselves. |
| 96 | + |
| 97 | +If `authorized?` returns `true`, the component will be rendered. If it returns `false`, |
| 98 | +the component will not be rendered. |
| 99 | + |
| 100 | +A public isolated component therefore needs an `authorized?` method simply returning `true`. |
| 101 | + |
| 102 | +This might sound complicated, but it is not. For example using devise you can access the controller helper `current_user` inside your isolated component, making authorization implementations as easy as: |
| 103 | + |
| 104 | +```ruby |
| 105 | +def authorized? |
| 106 | + current_user.present? |
| 107 | +end |
| 108 | +``` |
| 109 | + |
| 110 | +### Data acquisition |
| 111 | + |
| 112 | +Use the `prepare` method in order to gather needed information from long running queries or complex business logic or use methods. The `prepare` method is executed before the `response`. |
| 113 | + |
| 114 | +```ruby |
| 115 | +class BookingsList < IsolatedComponent |
| 116 | + |
| 117 | + def prepare |
| 118 | + @bookings = Booking.some_long_running_query |
| 119 | + end |
| 120 | + |
| 121 | + def response |
| 122 | + @bookings.each do |booking| |
| 123 | + paragraph text: booking.details |
| 124 | + end |
| 125 | + availabilities.each do |availability| |
| 126 | + paragraph text: availability.details |
| 127 | + end |
| 128 | + end |
| 129 | + |
| 130 | + def authorized? |
| 131 | + true |
| 132 | + end |
| 133 | + |
| 134 | + def availabilities |
| 135 | + Booking.some_long_runnning_availability_check |
| 136 | + end |
| 137 | + |
| 138 | +end |
| 139 | +``` |
| 140 | + |
| 141 | +Deferring such slow parts of your ui speeds up your page load significantly. But remember to always try to improve your query or logic performance as isolated components are not your general solution to fast page loads. |
| 142 | + |
| 143 | + |
| 144 | +### Loading animations |
| 145 | + |
| 146 | +Isolated components are wrapped in a special html structure allowing you to create animations while the components gets loaded or rerendered. It appends a loading class to the wrapping elements while the component is loading or rerendering. To learn more about how to animate loading isolated components checkout its [api documentation](/docs/api/100-components/async.md). |
| 147 | + |
| 148 | + |
| 149 | +## Complete documentation |
| 150 | + |
| 151 | +If you want to know all details about isolated components checkout its [api documentation](/docs/api/000-base/40-isolated_component.md). |
0 commit comments